@songsid/agend 2.3.0-beta.2 → 2.3.0-beta.20
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 +704 -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
package/dist/ui/chat-thread.js
CHANGED
|
@@ -28,6 +28,7 @@ const P = () => globalThis.AgendPreview;
|
|
|
28
28
|
/** #1672: a status line longer than this (lines, or characters) is folded until it is opened. */
|
|
29
29
|
export const STATUS_FOLD_LINES = 6;
|
|
30
30
|
export const STATUS_FOLD_CHARS = 600;
|
|
31
|
+
const REPLY = '<svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/></svg>';
|
|
31
32
|
const COPY = '<svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
|
32
33
|
|
|
33
34
|
/** Escapes & " ' < >: safe for text and inside a quoted attribute. */
|
|
@@ -39,6 +40,41 @@ export const msgKey = (x) => `${x.boot}-${x.id}`;
|
|
|
39
40
|
*/
|
|
40
41
|
export const THREAD_TAIL = 24;
|
|
41
42
|
export const THREAD_CHUNK = 40;
|
|
43
|
+
/** #2158: a reader's scroll (smooth, momentum) is theirs until it has been this quiet since their last input or scroll. */
|
|
44
|
+
export const READER_QUIET_MS = 250;
|
|
45
|
+
const SCROLL_KEYS = new Set(["PageUp", "PageDown", "ArrowUp", "ArrowDown", "Home", "End", " "]);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* #2053: a message's rendered Markdown, kept across renders and chat switches. A draw compares each message's html with
|
|
49
|
+
* its node's, so every render of a thread rendered every drawn message's Markdown again (~320 calls per 200-message
|
|
50
|
+
* switch). renderMarkdown's output depends only on the text, whether its HTML cards are on (an agent's message), and —
|
|
51
|
+
* for a text that has them — whether the page knows each server-emoji id and the name it has for each mention: the key
|
|
52
|
+
* holds exactly those, so a cached html is always what renderMarkdown would return now. Bounded: least recently used out.
|
|
53
|
+
*/
|
|
54
|
+
export const MD_CACHE_MAX = 4000;
|
|
55
|
+
const mdCache = new Map();
|
|
56
|
+
const EMOJI_IDS = /<a?:[A-Za-z0-9_]{2,32}:(\d{15,25})>/g;
|
|
57
|
+
const MENTION_IDS = /<(@!?|@&|#)(\d{15,25})>/g;
|
|
58
|
+
const MENTION_KINDS = { "@": "u", "@!": "u", "@&": "r", "#": "c" };
|
|
59
|
+
export function renderedMarkdown(text, cards, emojis, mentions) {
|
|
60
|
+
const src = typeof text === "string" ? text : String(text == null ? "" : text);
|
|
61
|
+
let sig = cards ? "c" : "-";
|
|
62
|
+
if (src.includes("<")) {
|
|
63
|
+
// Read as renderMarkdown reads it: it drops NUL first (its placeholder mark), so "<@\0id>" is a mention there too.
|
|
64
|
+
const seen = src.includes("\u0000") ? src.replace(/\u0000/g, "") : src;
|
|
65
|
+
for (const m of seen.matchAll(EMOJI_IDS)) sig += `|e${m[1]}${emojis && emojis(m[1]) ? 1 : 0}`;
|
|
66
|
+
for (const m of seen.matchAll(MENTION_IDS)) { const name = mentions ? mentions(MENTION_KINDS[m[1]], m[2]) : null; sig += `|m${m[1]}${m[2]}=${typeof name === "string" ? name.length + ":" + name : "-"}`; }
|
|
67
|
+
}
|
|
68
|
+
const key = `${sig}\u0000${src}`;
|
|
69
|
+
let html = mdCache.get(key);
|
|
70
|
+
if (html === undefined) html = R().renderMarkdown(src, { htmlCards: cards, emojis, mentions });
|
|
71
|
+
else mdCache.delete(key);
|
|
72
|
+
mdCache.set(key, html);
|
|
73
|
+
while (mdCache.size > MD_CACHE_MAX) mdCache.delete(mdCache.keys().next().value);
|
|
74
|
+
return html;
|
|
75
|
+
}
|
|
76
|
+
/** Tests only: forget every rendered Markdown. */
|
|
77
|
+
export function clearRenderedMarkdown() { mdCache.clear(); }
|
|
42
78
|
|
|
43
79
|
/**
|
|
44
80
|
* Bind a renderer to `list` (the thread element) inside `scroller`. opts: { t, tf, isUser(x), onJump({show, unseen}),
|
|
@@ -47,6 +83,17 @@ export const THREAD_CHUNK = 40;
|
|
|
47
83
|
*/
|
|
48
84
|
export function createThread(list, scroller, opts) {
|
|
49
85
|
const nodes = new Map(); // key → { html, node }
|
|
86
|
+
// A server emoji's picture that does not load reads `:name:` (its alt) instead of an empty box (error does not bubble:
|
|
87
|
+
// caught on the way down).
|
|
88
|
+
list.addEventListener("error", (e) => {
|
|
89
|
+
const img = e.target;
|
|
90
|
+
if (!img || img.tagName !== "IMG" || !img.classList || !img.classList.contains("emoji")) return;
|
|
91
|
+
const span = list.ownerDocument.createElement("span");
|
|
92
|
+
span.className = "emoji-text";
|
|
93
|
+
span.textContent = img.getAttribute("alt") || "";
|
|
94
|
+
span.title = img.getAttribute("title") || span.textContent;
|
|
95
|
+
img.replaceWith(span);
|
|
96
|
+
}, true);
|
|
50
97
|
let msgs = [];
|
|
51
98
|
let unseen = 0;
|
|
52
99
|
// The reader is at the newest message. While pinned, content that grows after it was drawn — an image loading, a
|
|
@@ -54,12 +101,15 @@ export function createThread(list, scroller, opts) {
|
|
|
54
101
|
// Only a move UP unpins: the browser's own scroll anchoring also fires scroll events when content grows (the view
|
|
55
102
|
// moves down or stays), and those must not count as the reader leaving the bottom. Reaching the bottom pins again.
|
|
56
103
|
let pinned = true;
|
|
57
|
-
|
|
104
|
+
// Where the view was (and how tall it was) at the last scroll the page saw or made: a move up from there unpins.
|
|
105
|
+
let lastTop = 0, lastView = 0;
|
|
106
|
+
const mark = () => { lastTop = scroller.scrollTop; lastView = scroller.clientHeight; };
|
|
58
107
|
// The oldest message drawn (by key): everything after it is drawn, so new messages never push the window. Null: none
|
|
59
108
|
// drawn yet — the next render draws the newest THREAD_TAIL and the rest follows (grow).
|
|
60
109
|
let firstKey = null;
|
|
61
|
-
let growTimer = null, growFrame = null, disposed = false;
|
|
110
|
+
let growTimer = null, growFrame = null, disposed = false, parked = false;
|
|
62
111
|
const cardRefresh = new WeakMap(); // card node → re-read availability (weak: a card that left takes its source with it)
|
|
112
|
+
const cardDrop = new WeakMap(); // card node → drop the HTML read it is waiting on (the chat left: park)
|
|
63
113
|
const tr = opts.t, trf = opts.tf;
|
|
64
114
|
const stopIn = (node) => (P() ? P().stopIn(node) : []);
|
|
65
115
|
/** #1770: write a message node's time label for now; unchanged text is not touched. */
|
|
@@ -92,7 +142,22 @@ export function createThread(list, scroller, opts) {
|
|
|
92
142
|
const buttons = x.role === "agent" ? replyButtonsHtml(x.buttons) : "";
|
|
93
143
|
// A person's click on a reply button shows as the button's label, never the line the agent was given (its value).
|
|
94
144
|
const clicked = x.role === "user" && x.click && typeof x.click.label === "string";
|
|
95
|
-
|
|
145
|
+
// #2272: reply to it — a hover strip at its top right (phones: swipe left or long-press; R on a focused message).
|
|
146
|
+
const key = msgKey(x);
|
|
147
|
+
const act = opts.canReply?.() === false ? "" : `<div data-trust="thread-control" class="msg-actions"><button type="button" class="chip-btn icon-only" data-act="reply" data-arg="${escAttr(key)}" title="${escAttr(tr("chat.reply"))}" aria-label="${escAttr(tr("chat.reply"))}">${REPLY}</button></div>`;
|
|
148
|
+
return `<div class="msg ${u ? "user" : "agent"}" data-key="${escAttr(key)}" tabindex="-1">${act}<div class="meta"><span class="sender">${escAttr(x.sender)}</span>${time}${ticks}</div>${quoteHtml(x.replyTo)}<div class="body">${clicked ? clickHtml(x.click) : `<div class="md">${renderedMarkdown(x.text, x.role === "agent", opts.emojis, opts.mentions)}</div>`}${R().attachmentsHtml(x.attachments, { htmlCards: x.role === "agent", goneTitle: tr("chat.attGone"), goneLabel: tr("chat.attUnavailable"), inlinePreviews: !PUBLIC_LINK() })}${buttons}</div>${tools}</div>`;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* #2272: what a reply quotes, above its body — the quoted sender and first words (or that it had files), as the server
|
|
152
|
+
* read them when it was sent; a button that shows that message. A guest that may not see it gets only "an earlier
|
|
153
|
+
* message". Text only: everything is escaped.
|
|
154
|
+
*/
|
|
155
|
+
function quoteHtml(q) {
|
|
156
|
+
if (!q || typeof q !== "object" || typeof q.boot !== "string" || typeof q.id !== "number") return "";
|
|
157
|
+
const words = q.hidden ? tr("chat.replyHidden")
|
|
158
|
+
: (typeof q.text === "string" && q.text) || (q.media === "photo" ? tr("chat.replyPhoto") : q.media === "document" ? tr("chat.replyFile") : "");
|
|
159
|
+
const who = !q.hidden && typeof q.sender === "string" && q.sender ? `<span class="quote-sender">${escAttr(q.sender)}</span> ` : "";
|
|
160
|
+
return `<button type="button" data-trust="thread-control" class="msg-quote" data-act="jumpQuote" data-arg="${escAttr(`${q.boot}-${q.id}`)}" title="${escAttr(tr("chat.replyJump"))}"><span class="quote-mark">${REPLY}</span> ${who}<span class="quote-text">${escAttr(words)}</span></button>`;
|
|
96
161
|
}
|
|
97
162
|
/**
|
|
98
163
|
* A button click as a chip: 🔘 and the label (escaped: the label is the agent's text). With `of` (live, while its
|
|
@@ -106,10 +171,18 @@ export function createThread(list, scroller, opts) {
|
|
|
106
171
|
: `<span class="click-text">${inner}</span>`}</div>`;
|
|
107
172
|
}
|
|
108
173
|
function trialNow() { try { const t = opts.serverNow ? opts.serverNow() : Date.now(); return Number.isFinite(t) ? t : Infinity; } catch { return Infinity; } }
|
|
174
|
+
const trialReasons = new Set(["checking", "localAdmin", "chatChanged", "busy", "safe", "effectsOff", "reducedMotion", "saveData", "unsupported", "transport", "inactive", "dialog", "expired"]);
|
|
175
|
+
function trialStatus(message) {
|
|
176
|
+
const reason = opts.effectTrialStatus?.(message);
|
|
177
|
+
return reason === "ready" || trialReasons.has(reason) ? reason : opts.canTryEffect?.(message) === true ? "ready" : "localAdmin";
|
|
178
|
+
}
|
|
109
179
|
function trialHtml(offer, time, message) {
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
|
|
180
|
+
const reason = trialNow() >= offer.expiresAt ? "expired" : PUBLIC_LINK() ? "localAdmin" : trialStatus(message);
|
|
181
|
+
const rawFailure = opts.effectTrialFailure?.(message), failure = typeof rawFailure === "string" ? rawFailure.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 80) : null;
|
|
182
|
+
const available = reason === "ready", fix = !PUBLIC_LINK() && !available && reason !== "expired" && reason !== "checking" && typeof opts.fixEffectTrial === "function";
|
|
183
|
+
const label = available ? "chat.effectTrialStart" : reason === "busy" ? "chat.effectTrialStopOther" : reason === "localAdmin" ? "chat.effectTrialSignIn" : reason === "reducedMotion" ? "chat.effectTrialPlayMotion" : "chat.effectTrialSettings";
|
|
184
|
+
const button = available || fix ? `<button type="button" class="btn btn-sm effect-trial-start" data-act="effectTrial" data-arg="${escAttr(msgKey(message))}">${escAttr(tr(label))}</button>` : "";
|
|
185
|
+
return `<div class="msg status-line"><div class="meta">${time}</div><div class="body"><div class="effect-trial-offer" data-trust="thread-control"><strong>${escAttr(tr("chat.effectTrial"))}: ${escAttr(offer.title)}</strong><div>${escAttr(tr(available ? "chat.effectTrialLocal" : "chat.effectTrialReason_" + reason))}</div>${failure ? `<div class="effect-trial-failure" role="status">${escAttr(tr("chat.effectTrialStopped"))}: ${escAttr(failure)}</div>` : ""}${button}</div></div></div>`;
|
|
113
186
|
}
|
|
114
187
|
function armTrials() {
|
|
115
188
|
if (trialTimer !== null) clearTimeout(trialTimer); trialTimer = null;
|
|
@@ -269,7 +342,7 @@ export function createThread(list, scroller, opts) {
|
|
|
269
342
|
const note = el("div", "pv-note"); note.setAttribute("data-trust", "thread-control");
|
|
270
343
|
const body = el("div", "tc-body"); body.hidden = true;
|
|
271
344
|
ph.append(head, note, body);
|
|
272
|
-
let text = null, reading =
|
|
345
|
+
let text = null, reading = 0, reads = 0, all = false;
|
|
273
346
|
const draw = () => {
|
|
274
347
|
body.textContent = "";
|
|
275
348
|
const lines = text.split(/\r?\n/);
|
|
@@ -305,12 +378,15 @@ export function createThread(list, scroller, opts) {
|
|
|
305
378
|
body.append(more);
|
|
306
379
|
}
|
|
307
380
|
};
|
|
381
|
+
// #2053: as an HTML card's read (cardDrop), one still waiting when the chat left is dropped, the card not left Loading.
|
|
382
|
+
cardDrop.set(ph, () => { if (reading) { reading = 0; note.textContent = ""; } });
|
|
308
383
|
show.onclick = async () => {
|
|
309
384
|
if (reading) return;
|
|
310
385
|
if (text !== null) { body.hidden = !body.hidden; show.textContent = tr(body.hidden ? "chat.tcShow" : "chat.tcHide"); return; }
|
|
311
|
-
reading =
|
|
386
|
+
const mine = reading = ++reads; note.textContent = tr("chat.pvLoading");
|
|
312
387
|
const r = await loadHtmlAttachment({ id: att.id, name: att.name, size: att.size });
|
|
313
|
-
reading
|
|
388
|
+
if (reading !== mine) return; // dropped (its chat left meanwhile): a new click reads again
|
|
389
|
+
reading = 0;
|
|
314
390
|
if (!ph.isConnected) return; // the card left the page while it was read: nothing to draw
|
|
315
391
|
if (!r.ok) { note.textContent = r.reason === "over" ? tr("chat.tcOver") : attReason(r.reason); return; }
|
|
316
392
|
note.textContent = "";
|
|
@@ -448,6 +524,10 @@ export function createThread(list, scroller, opts) {
|
|
|
448
524
|
optB.onclick = () => { menu.open = false; opts.setPreviewOptIn(!P().optedIn()); };
|
|
449
525
|
neverB.onclick = () => { menu.open = false; P().setNever(!P().never()); refreshCards(); };
|
|
450
526
|
cardRefresh.set(ph, refresh);
|
|
527
|
+
// #2053: a kept card is shown again later. A read still waiting when its chat left is dropped then: its answer —
|
|
528
|
+
// whenever it comes — starts nothing (no preview, no panel without a new click), and the card is not left Loading
|
|
529
|
+
// (shown again, it is refreshed: refreshCards).
|
|
530
|
+
cardDrop.set(ph, () => { reading = 0; });
|
|
451
531
|
refresh(stoppedKeys.includes(key) ? tr("chat.pvChanged") : null);
|
|
452
532
|
}
|
|
453
533
|
/** Re-read this device's preview choice on every card in this thread. */
|
|
@@ -462,12 +542,45 @@ export function createThread(list, scroller, opts) {
|
|
|
462
542
|
// newest bubble clipped behind it, and the next growth past 48 px put the pill over it.
|
|
463
543
|
const atBottom = () => R().isNearBottom(scroller.scrollTop, scroller.clientHeight, scroller.scrollHeight, 1);
|
|
464
544
|
function reportJump() { opts.onJump({ show: !nearBottom(), unseen }); }
|
|
465
|
-
function toBottom() { scroller.scrollTop = scroller.scrollHeight; pinned = true;
|
|
545
|
+
function toBottom() { snap(); scroller.scrollTop = scroller.scrollHeight; pinned = true; mark(); }
|
|
546
|
+
/**
|
|
547
|
+
* #2184: a thread's height is rarely a whole number of pixels, and a pinned view's offset is: so each time older messages
|
|
548
|
+
* were drawn above, the whole view moved by the change in that fraction (0.6–0.7 px — the faint jitter of a chat
|
|
549
|
+
* opened cold). A sub-pixel margin above the list keeps the thread's height whole, so the bottom never moves.
|
|
550
|
+
*/
|
|
551
|
+
// #2188: one read (the list's box excludes its own margin, so nothing is reset first) and a write only on a change —
|
|
552
|
+
// a reset-then-measure laid a 200-message thread out twice on every pinned draw (2.3 s at 4× CPU on a kept chat).
|
|
553
|
+
let snapped = null;
|
|
554
|
+
function snap() {
|
|
555
|
+
const h = list.getBoundingClientRect().height, f = h - Math.floor(h);
|
|
556
|
+
const want = f > 0.001 && f < 0.999 ? (1 - f) + "px" : "0px";
|
|
557
|
+
if (want !== snapped) { snapped = want; list.style.setProperty("--thread-snap", want); }
|
|
558
|
+
}
|
|
466
559
|
// Sizes change without a scroll event (layout after the render, the dock under the thread): while pinned, the view
|
|
467
560
|
// stays flush with the bottom; a reader who scrolled up is never moved.
|
|
468
561
|
const resized = () => { if (pinned && !atBottom()) toBottom(); reportJump(); };
|
|
469
|
-
|
|
562
|
+
let ro = typeof ResizeObserver === "function" ? new ResizeObserver(resized) : null;
|
|
470
563
|
if (ro) { ro.observe(list); ro.observe(scroller); }
|
|
564
|
+
// #2158: the reader's own hand on the thread — a wheel, a touch, the scrollbar (a pointer held), a scrolling key
|
|
565
|
+
// outside a text field — and the scroll that follows it until it has been quiet READER_QUIET_MS.
|
|
566
|
+
let inputAt = -Infinity, held = false;
|
|
567
|
+
const touched = () => { inputAt = performance.now(); };
|
|
568
|
+
const hold = (on) => () => { held = on; touched(); };
|
|
569
|
+
const onKey = (ev) => {
|
|
570
|
+
const el = ev.target;
|
|
571
|
+
if (SCROLL_KEYS.has(ev.key) && !(el && (el.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName || "")))) touched();
|
|
572
|
+
};
|
|
573
|
+
const onScroller = [["wheel", touched], ["touchstart", hold(true)], ["touchend", hold(false)], ["touchcancel", hold(false)], ["pointerdown", hold(true)]];
|
|
574
|
+
const onDocument = [["pointerup", hold(false)], ["pointercancel", hold(false)], ["keydown", onKey]];
|
|
575
|
+
/** Listen for the reader's input on `sc` (and its document: a pointer let go anywhere, a key), or stop. */
|
|
576
|
+
function watchReader(sc, on) {
|
|
577
|
+
const doc = sc.ownerDocument, how = on ? "addEventListener" : "removeEventListener";
|
|
578
|
+
for (const [type, fn] of onScroller) sc[how](type, fn, { passive: true });
|
|
579
|
+
if (doc) for (const [type, fn] of onDocument) doc[how](type, fn, true);
|
|
580
|
+
if (!on) held = false;
|
|
581
|
+
}
|
|
582
|
+
const readerActive = () => held || performance.now() - inputAt < READER_QUIET_MS;
|
|
583
|
+
watchReader(scroller, true);
|
|
471
584
|
|
|
472
585
|
/** Where drawing starts in `msgs`: at the oldest drawn message, or (none yet) the newest THREAD_TAIL. */
|
|
473
586
|
function startIndex() {
|
|
@@ -481,14 +594,14 @@ export function createThread(list, scroller, opts) {
|
|
|
481
594
|
/** Draw the next chunk of older messages, keeping the reader where they were; then the next, until all are drawn. */
|
|
482
595
|
function grow() {
|
|
483
596
|
growTimer = growFrame = null;
|
|
484
|
-
if (disposed) return;
|
|
597
|
+
if (disposed || parked) return; // the chat left: nothing more is drawn (a kept one fills in when shown again)
|
|
485
598
|
const at = startIndex();
|
|
486
599
|
if (at <= 0) return;
|
|
487
600
|
firstKey = msgKey(msgs[Math.max(0, at - THREAD_CHUNK)]);
|
|
488
601
|
const fromBottom = scroller.scrollHeight - scroller.scrollTop;
|
|
489
602
|
draw({ older: true }); // drawn above the reader: never "new below" (render alone counts those)
|
|
490
603
|
if (pinned) toBottom();
|
|
491
|
-
else { scroller.scrollTop = scroller.scrollHeight - fromBottom;
|
|
604
|
+
else { scroller.scrollTop = scroller.scrollHeight - fromBottom; mark(); }
|
|
492
605
|
scheduleGrow();
|
|
493
606
|
}
|
|
494
607
|
/** After the next frame has painted (the click's own frame stays light), draw a chunk more. */
|
|
@@ -529,7 +642,7 @@ export function createThread(list, scroller, opts) {
|
|
|
529
642
|
if ("restore" in renderOpts) {
|
|
530
643
|
unseen = 0;
|
|
531
644
|
if (renderOpts.restore == null) toBottom();
|
|
532
|
-
else { scroller.scrollTop = renderOpts.restore; pinned = nearBottom();
|
|
645
|
+
else { scroller.scrollTop = renderOpts.restore; pinned = nearBottom(); mark(); }
|
|
533
646
|
} else if (stick) toBottom();
|
|
534
647
|
else unseen += added;
|
|
535
648
|
reportJump();
|
|
@@ -559,14 +672,39 @@ export function createThread(list, scroller, opts) {
|
|
|
559
672
|
}
|
|
560
673
|
armDay();
|
|
561
674
|
armPolls();
|
|
675
|
+
rove();
|
|
562
676
|
return added;
|
|
563
677
|
}
|
|
678
|
+
/**
|
|
679
|
+
* #2272 (review): one message in the thread is in the Tab order — the last one focused while it is still drawn, else
|
|
680
|
+
* the newest; ↑/↓ move between messages (chat-msg-actions.js). The rest are -1, so Tab passes the thread in one step.
|
|
681
|
+
*/
|
|
682
|
+
let roving = null;
|
|
683
|
+
function rove(key) {
|
|
684
|
+
if (key !== undefined) roving = key;
|
|
685
|
+
const at = (roving && nodes.has(roving) ? nodes.get(roving).node : null) || [...list.querySelectorAll(".msg[data-key]")].at(-1) || null;
|
|
686
|
+
for (const el of list.querySelectorAll('.msg[data-key][tabindex="0"]')) if (el !== at) el.setAttribute("tabindex", "-1");
|
|
687
|
+
if (at) at.setAttribute("tabindex", "0");
|
|
688
|
+
}
|
|
564
689
|
|
|
565
690
|
function onScroll() {
|
|
691
|
+
// #2158: the thread got shorter under a pinned view (text settling on a cold load, a card) and the browser clamped
|
|
692
|
+
// scrollTop to its new end — for the view as tall as it was then; the view got shorter too before WebKit sent that
|
|
693
|
+
// clamp's scroll event (it comes before the ResizeObserver). A view resting exactly at the bottom of the view as it
|
|
694
|
+
// was at the last mark, with no hand of the reader's on the thread, was put there by the browser: still pinned, back
|
|
695
|
+
// at the bottom. Geometry alone can't tell (a reader's move up by the dock's growth lands there too): any input of
|
|
696
|
+
// theirs, and the scroll after it, is theirs and unpins as before. The page's own scrolls (reveal) have no input but
|
|
697
|
+
// don't land there.
|
|
698
|
+
const reader = readerActive();
|
|
699
|
+
if (reader) touched(); // the reader's scroll goes on (smooth, momentum): still theirs while it does
|
|
700
|
+
else if (pinned && !atBottom() && Math.abs(scroller.scrollHeight - lastView - scroller.scrollTop) <= 1) toBottom();
|
|
566
701
|
const bottom = nearBottom(), top = scroller.scrollTop;
|
|
567
|
-
|
|
702
|
+
// #2087: pinned only at the exact bottom — a reader still wheeling through the last 48 px was pinned there, so
|
|
703
|
+
// the next resize (a picture, a card, the dock) pulled the view the rest of the way down under them. Near the bottom
|
|
704
|
+
// still counts for a new message (render follows it) and for the pill; nothing else moves a view the reader moves.
|
|
705
|
+
if (atBottom()) pinned = true;
|
|
568
706
|
else if (top < lastTop) pinned = false;
|
|
569
|
-
|
|
707
|
+
mark();
|
|
570
708
|
if (bottom) unseen = 0;
|
|
571
709
|
reportJump();
|
|
572
710
|
return bottom ? null : scroller.scrollTop;
|
|
@@ -628,21 +766,66 @@ export function createThread(list, scroller, opts) {
|
|
|
628
766
|
// Only the current host-produced keyed card and a real local click can request admission. No token is read from DOM.
|
|
629
767
|
const key = el.dataset.arg, row = msgs.find(x => msgKey(x) === key), owner = nodes.get(key), node = owner?.node, offer = effectTrialMessage(row);
|
|
630
768
|
if (PUBLIC_LINK() || e.defaultPrevented || e.isTrusted !== true || owner?.trialButton !== el || el.className !== "btn btn-sm effect-trial-start" || !node?.contains(el) || el.closest(".effect-trial-offer")?.getAttribute("data-trust") !== "thread-control"
|
|
631
|
-
|| !offer || trialNow() >= offer.expiresAt
|
|
632
|
-
opts.startEffectTrial?.(row);
|
|
769
|
+
|| !offer || trialNow() >= offer.expiresAt) return;
|
|
770
|
+
if (trialStatus(row) === "ready" && opts.canTryEffect?.(row) === true) opts.startEffectTrial?.(row);
|
|
771
|
+
else opts.fixEffectTrial?.(row, trialStatus(row));
|
|
633
772
|
} else if (act === "showButtons") { const x = msgs.find(y => y.buttons && y.buttons.id === el.dataset.arg); if (x) reveal(msgKey(x)); }
|
|
773
|
+
else if (act === "reply") { const x = msgs.find(y => msgKey(y) === el.dataset.arg); if (x) opts.startReply?.(x); }
|
|
774
|
+
else if (act === "jumpQuote") void jumpTo(el.dataset.arg);
|
|
634
775
|
}
|
|
776
|
+
/**
|
|
777
|
+
* #2272: show the message a reply quotes, flashed — drawn now, or after the chat reads back the longest history the
|
|
778
|
+
* server keeps; otherwise it says it is no longer there.
|
|
779
|
+
*/
|
|
780
|
+
async function jumpTo(key) {
|
|
781
|
+
if (typeof key !== "string" || !key) return;
|
|
782
|
+
if (reveal(key)) return;
|
|
783
|
+
const found = opts.findQuoted ? await opts.findQuoted(key) : false;
|
|
784
|
+
if (!found || !reveal(key)) opts.notice?.(tr("chat.replyGone"));
|
|
785
|
+
}
|
|
786
|
+
/** #2272: a drawn message's row (for the message menu and gestures), or undefined. */
|
|
787
|
+
function messageOf(key) { return msgs.find(y => msgKey(y) === key); }
|
|
635
788
|
|
|
636
789
|
/** The thread goes (the chat left, or another instance opened): every preview in it stops first. */
|
|
637
|
-
|
|
790
|
+
/**
|
|
791
|
+
* #2053: kept for a later visit — the chat left as it always did (every preview stops: "leave"), but its drawn nodes
|
|
792
|
+
* stay. Nothing of it runs meanwhile: no timer (day labels, polls, effect trials, filling in), no size observer; the
|
|
793
|
+
* caller drops its subscriptions. Shown again: attach, then render — only what changed is drawn.
|
|
794
|
+
*/
|
|
795
|
+
function park() {
|
|
796
|
+
parked = true;
|
|
797
|
+
watchReader(scroller, false);
|
|
798
|
+
if (P()) P().stopAll("leave");
|
|
799
|
+
for (const node of list.querySelectorAll(".html-card[data-att], .text-card[data-att]")) { const f = cardDrop.get(node); if (f) f(); }
|
|
800
|
+
if (ro) { ro.disconnect(); ro = null; }
|
|
801
|
+
if (trialTimer !== null) clearTimeout(trialTimer); trialTimer = null;
|
|
802
|
+
if (growFrame !== null && typeof cancelAnimationFrame === "function") cancelAnimationFrame(growFrame);
|
|
803
|
+
if (growTimer !== null) clearTimeout(growTimer);
|
|
804
|
+
growTimer = growFrame = null;
|
|
805
|
+
if (offDay) { offDay(); offDay = null; }
|
|
806
|
+
if (pollTick) { clearInterval(pollTick); pollTick = null; }
|
|
807
|
+
}
|
|
808
|
+
/** In view in `sc` (the view's scroller): sizes watched there from now on; the caller restores the position. */
|
|
809
|
+
function attach(sc) {
|
|
810
|
+
if (!parked) watchReader(scroller, false); // moved from the scroller it was made with
|
|
811
|
+
parked = false;
|
|
812
|
+
scroller = sc;
|
|
813
|
+
watchReader(sc, true);
|
|
814
|
+
if (ro) ro.disconnect();
|
|
815
|
+
ro = typeof ResizeObserver === "function" ? new ResizeObserver(resized) : null;
|
|
816
|
+
if (ro) { ro.observe(list); ro.observe(sc); }
|
|
817
|
+
}
|
|
818
|
+
/** `own`: only this thread's previews stop (a thread let go while another chat is in view); else every one, as leaving. */
|
|
819
|
+
function dispose(own = false) {
|
|
638
820
|
disposed = true;
|
|
821
|
+
if (!parked) watchReader(scroller, false);
|
|
639
822
|
if (trialTimer !== null) clearTimeout(trialTimer); trialTimer = null;
|
|
640
823
|
if (ro) ro.disconnect();
|
|
641
|
-
if (P()) P().stopAll("leave");
|
|
824
|
+
if (P()) { if (own) P().stopIn(list); else P().stopAll("leave"); }
|
|
642
825
|
nodes.clear();
|
|
643
826
|
armDay();
|
|
644
827
|
armPolls();
|
|
645
828
|
}
|
|
646
829
|
|
|
647
|
-
return { render, onScroll, onClick, jumpLatest, reveal, refreshCards, dispose, nodes, get unseen() { return unseen; } };
|
|
830
|
+
return { render, onScroll, onClick, jumpLatest, reveal, jumpTo, messageOf, rove, refreshCards, park, attach, dispose, nodes, get unseen() { return unseen; } };
|
|
648
831
|
}
|
|
@@ -5,7 +5,7 @@ import { chatEffectIdentity } from "/assets/effect-chat-runtime.js";
|
|
|
5
5
|
const owners = new WeakSet(), safe = fn => { try { return fn(); } catch { return null; } };
|
|
6
6
|
const LIMIT = 128 * 1024;
|
|
7
7
|
export function createEffectTrialController({ window: host, current, allowed, onStart, onChange = () => {} }) {
|
|
8
|
-
let dead = false, active = null, physical = null, previous = null, broken = false, clockBusy = false;
|
|
8
|
+
let dead = false, active = null, physical = null, previous = null, broken = false, clockBusy = false, endedReason = null, endedOffer = null;
|
|
9
9
|
const cleanup = [], releases = [], identity = safe(() => chatEffectIdentity(host));
|
|
10
10
|
const tell = () => { if (!dead) safe(onChange); };
|
|
11
11
|
function clock() {
|
|
@@ -26,11 +26,16 @@ export function createEffectTrialController({ window: host, current, allowed, on
|
|
|
26
26
|
}
|
|
27
27
|
function end(op, reason = "stopped") {
|
|
28
28
|
if (!op || op.dead) return false;
|
|
29
|
-
op.dead = true; if (active === op) active = null;
|
|
29
|
+
op.dead = true; if (active === op) active = null;
|
|
30
|
+
endedReason = typeof reason === "string" && /^[a-z][a-z0-9_]{0,79}$/.test(reason) ? reason : "trial_failed";
|
|
31
|
+
endedOffer = op.offer.offer;
|
|
32
|
+
safe(() => op.resolve?.(false));
|
|
30
33
|
safe(() => host.clearTimeout(op.timer)); safe(() => op.controller.abort());
|
|
31
34
|
if (op.reply && !op.stopQueued) { op.stopQueued = true; cleanup.push(op); }
|
|
32
35
|
// Physical paint is removed before callbacks, cleanup I/O or another explicit click.
|
|
33
|
-
safe(() => op.dispose?.()); op.dispose = null;
|
|
36
|
+
safe(() => op.dispose?.()); op.dispose = null;
|
|
37
|
+
if (!["stopped", "replaced"].includes(endedReason)) safe(() => host.console?.warn("[AgEnD effects] Try stopped:", endedReason));
|
|
38
|
+
tell(); drain(); return true;
|
|
34
39
|
}
|
|
35
40
|
function arm(op) {
|
|
36
41
|
safe(() => host.clearTimeout(op.timer));
|
|
@@ -67,7 +72,7 @@ export function createEffectTrialController({ window: host, current, allowed, on
|
|
|
67
72
|
if (dead) return; dead = true; end(active); for (const off of releases.splice(0)) safe(off); tell();
|
|
68
73
|
}
|
|
69
74
|
const api = { ok: false, start: async () => false, stop: () => end(active), sync() { const op = active; if (op && !eligible(op)) end(op, "trial_ended"); },
|
|
70
|
-
snapshot: () => Object.freeze({ busy: !!physical || cleanup.length > 0, phase: active ? active.reply ? "running" : "starting" : "idle" }), dispose };
|
|
75
|
+
snapshot: () => Object.freeze({ busy: !!physical || cleanup.length > 0, phase: active ? active.reply ? "running" : "starting" : "idle", reason: active ? null : endedReason, offer: active ? active.offer.offer : endedOffer }), dispose };
|
|
71
76
|
if (!identity || owners.has(host) || typeof current !== "function" || typeof allowed !== "function" || typeof onStart !== "function"
|
|
72
77
|
|| typeof host.AgendAuth?.trialSession !== "function" || typeof host.AgendAuth?.onSessionEnd !== "function") return Object.freeze(api);
|
|
73
78
|
owners.add(host);
|
|
@@ -75,7 +80,7 @@ export function createEffectTrialController({ window: host, current, allowed, on
|
|
|
75
80
|
const off = host.AgendAuth.onSessionEnd(dispose);
|
|
76
81
|
if (dead) { safe(off); return Object.freeze(api); }
|
|
77
82
|
releases.push(off);
|
|
78
|
-
for (const [target, type, fn] of [[host, "blur", () => end(active)], [host, "pagehide", dispose], [host.document, "visibilitychange", () => { if (host.document.visibilityState !== "visible") end(active); }]]) {
|
|
83
|
+
for (const [target, type, fn] of [[host, "blur", () => end(active, "trial_window_blur")], [host, "pagehide", dispose], [host.document, "visibilitychange", () => { if (host.document.visibilityState !== "visible") end(active, "hidden"); }]]) {
|
|
79
84
|
target.addEventListener(type, fn); releases.push(() => target.removeEventListener(type, fn));
|
|
80
85
|
}
|
|
81
86
|
} catch { dispose(); return Object.freeze(api); }
|
|
@@ -86,7 +91,7 @@ export function createEffectTrialController({ window: host, current, allowed, on
|
|
|
86
91
|
const offer = safe(() => effectTrialMessage(message)), instance = safe(() => message.instance), began = clock();
|
|
87
92
|
if (!offer || began === null || began + 10000 > Number.MAX_SAFE_INTEGER || safe(() => host.Date.now() < offer.expiresAt) !== true) return false;
|
|
88
93
|
const op = { instance, offer, began, until: began + 10000, controller: new host.AbortController(), timer: null, reply: null, dead: false, stopQueued: false, dispose: null };
|
|
89
|
-
active = op; const token = {}; physical = token;
|
|
94
|
+
endedReason = null; endedOffer = null; active = op; const token = {}; physical = token;
|
|
90
95
|
const cancelled = new Promise(resolve => { op.resolve = resolve; });
|
|
91
96
|
const work = Promise.resolve().then(async () => {
|
|
92
97
|
if (!eligible(op)) return false;
|
|
@@ -105,12 +110,12 @@ export function createEffectTrialController({ window: host, current, allowed, on
|
|
|
105
110
|
op.until = began + reply.remainingMs;
|
|
106
111
|
if (op.until > Number.MAX_SAFE_INTEGER || !eligible(op)) return false;
|
|
107
112
|
arm(op);
|
|
108
|
-
const handle = Object.freeze({ reply, startedAt: began, instance, current: () => eligible(op), stop: () => end(op), });
|
|
113
|
+
const handle = Object.freeze({ reply, startedAt: began, instance, current: () => eligible(op), stop: (reason = "stopped") => end(op, reason), });
|
|
109
114
|
const disposer = onStart(handle);
|
|
110
115
|
if (typeof disposer !== "function") return false;
|
|
111
116
|
if (!eligible(op)) { safe(disposer); return false; }
|
|
112
117
|
op.dispose = disposer; tell(); return eligible(op);
|
|
113
|
-
}).catch(() => false).then(ok => { if (!ok) end(op); return ok; }).finally(() => { if (physical === token) physical = null; tell(); drain(); });
|
|
118
|
+
}).catch(() => false).then(ok => { if (!ok) end(op, "trial_start_failed"); return ok; }).finally(() => { if (physical === token) physical = null; tell(); drain(); });
|
|
114
119
|
// The logical caller may stop immediately, while the physical request/cancel still owns the lane.
|
|
115
120
|
tell();
|
|
116
121
|
return Promise.race([work, cancelled]);
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/** #1629: fixed host producer. Creates no frame or request; the admitted app controller owns its lease. */
|
|
2
2
|
import { html, useLayoutEffect, useRef } from "/assets/app-html.js";
|
|
3
3
|
import { register, t } from "/assets/app-i18n.js";
|
|
4
|
+
import { createEffectTrialShadow } from "/assets/effect-trial-shadow.js";
|
|
4
5
|
import { createEffectTrialStage } from "/assets/effect-trial-stage.js";
|
|
5
6
|
register("effectTrial", {
|
|
6
7
|
en: { label: "Trial", stop: "Stop trial", close: "Close", note: "A bounded sample. Nothing is applied or saved." },
|
|
7
8
|
"zh-TW": { label: "試播", stop: "停止試播", close: "關閉", note: "僅在此區塊試播,不會套用或儲存。" },
|
|
8
9
|
});
|
|
9
10
|
/** The caller owns authenticated admission, lease/deadline and shared pool. No script/HTML is rendered here. */
|
|
10
|
-
export function EffectTrialPanel({ window: host = globalThis.window, binding, title, current,
|
|
11
|
-
const panel = useRef(null),
|
|
12
|
-
latest.current = { current,
|
|
11
|
+
export function EffectTrialPanel({ window: host = globalThis.window, binding, title, current, onStage, onStop, onClose }) {
|
|
12
|
+
const panel = useRef(null), life = useRef(null), latest = useRef(null);
|
|
13
|
+
latest.current = { current, onStage, onStop, onClose };
|
|
13
14
|
const stop = close => {
|
|
14
15
|
const entry = life.current; if (!entry || (close ? entry.closed : entry.dead)) return;
|
|
15
16
|
if (close) entry.closed = true;
|
|
@@ -17,21 +18,23 @@ export function EffectTrialPanel({ window: host = globalThis.window, binding, ti
|
|
|
17
18
|
try { (close ? entry.onClose : entry.onStop)?.("stopped"); } catch { /* lease already ended */ }
|
|
18
19
|
};
|
|
19
20
|
useLayoutEffect(() => {
|
|
20
|
-
const entry = { dead: false, closed: false, api: null, onStop, onClose }; life.current = entry;
|
|
21
|
+
const entry = { dead: false, closed: false, api: null, shadow: null, onStop, onClose }; life.current = entry;
|
|
21
22
|
const ended = reason => { if (entry.dead) return; entry.dead = true; try { entry.onStop?.(reason); } catch { /* never revive */ } };
|
|
22
23
|
try {
|
|
23
|
-
entry.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
entry.shadow = createEffectTrialShadow({ window: host, panel: panel.current, title,
|
|
25
|
+
labels: { label: t("effectTrial.label"), close: t("effectTrial.close"), stop: t("effectTrial.stop"), note: t("effectTrial.note") },
|
|
26
|
+
onStop: () => stop(false), onClose: () => stop(true), onError: ended,
|
|
27
|
+
onReady(shell) {
|
|
28
|
+
if (entry.dead || life.current !== entry) return;
|
|
29
|
+
entry.api = createEffectTrialStage({ window: host, panel: panel.current, stage: shell.stage, binding,
|
|
30
|
+
current: own => !entry.dead && life.current === entry && latest.current.current?.(own) === true, onStop: ended });
|
|
31
|
+
if (!entry.api) ended("stage_unavailable");
|
|
32
|
+
else { try { latest.current.onStage?.(entry.api); } catch { entry.api.dispose(); ended("stage_unavailable"); } }
|
|
33
|
+
} });
|
|
34
|
+
if (!entry.shadow) ended("stage_unavailable");
|
|
28
35
|
} catch { entry.api?.dispose(); ended("stage_unavailable"); }
|
|
29
|
-
return () => { entry.api?.dispose(); entry.dead = true; if (life.current === entry) life.current = null; };
|
|
36
|
+
return () => { entry.api?.dispose(); ended("stopped"); entry.shadow?.dispose(); entry.dead = true; if (life.current === entry) life.current = null; };
|
|
30
37
|
}, [host, binding]);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
<div class="effect-trial-controls"><button type="button" class="btn" onClick=${() => stop(false)}>${t("effectTrial.stop")}</button></div>
|
|
34
|
-
<div class="effect-trial-body"><div ref=${stage} class="effect-trial-stage" aria-hidden="true"></div></div>
|
|
35
|
-
<p class="effect-trial-note">${t("effectTrial.note")}</p>
|
|
36
|
-
</aside>`;
|
|
38
|
+
useLayoutEffect(() => { life.current?.shadow?.text(title, { label: t("effectTrial.label"), close: t("effectTrial.close"), stop: t("effectTrial.stop"), note: t("effectTrial.note") }); });
|
|
39
|
+
return html`<aside ref=${panel} data-trust="preview" class="pv-panel effect-trial-panel" aria-label=${t("effectTrial.label")}></aside>`;
|
|
37
40
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// #2193: the web effects runtime stays unloaded until there is something it could play for this viewer. Before that,
|
|
2
|
+
// this module is all there is: a few store subscriptions comparing fields the page already has (no observer, listener,
|
|
3
|
+
// timer, layer or request). The runtime itself (app-effects.js and everything it imports) is the reviewed code,
|
|
4
|
+
// unchanged; it is imported and started here, once, the first time one of these holds:
|
|
5
|
+
// - the open chat has Canvas Worker placements: its status row's effect_chat_v (the hash of its placements, taken
|
|
6
|
+
// before the server's region filtering — so it can only wake for nothing, never miss something) is not the hash of
|
|
7
|
+
// an empty list;
|
|
8
|
+
// - the signed-in person has a personal effect slot chosen (fx.page/header/sidebar/box/cursor — app-prefs);
|
|
9
|
+
// - an agent's "Try" offer is shown (wakeEffects() from the chat), or Settings saved a personal effect.
|
|
10
|
+
// Once started it stays for the page: the runtime is one episode per window (a disposed one cannot start again).
|
|
11
|
+
// Owner CSS is not the runtime's: owner-css.js is installed by the app shell and works with the runtime never loaded.
|
|
12
|
+
import { appStore } from "/assets/app-store.js";
|
|
13
|
+
import { navStore } from "/assets/app-nav.js";
|
|
14
|
+
import { ownerEffectsChosen, onOwnerEffectsChanged } from "/assets/app-prefs.js";
|
|
15
|
+
|
|
16
|
+
/** effect_chat_v of a chat with no placements: sha256(JSON.stringify([])), first 32 hex (fleet-manager's status row). */
|
|
17
|
+
export const EMPTY_CHAT_EFFECTS = "4f53cda18c2baa0c0354bb5f9a3ecbe5";
|
|
18
|
+
|
|
19
|
+
let mod = null, waking = null, chatStore = null, started = false;
|
|
20
|
+
const awakeListeners = new Set();
|
|
21
|
+
|
|
22
|
+
/** Whether `name`'s chat has Canvas Worker placements (as the status rows say). */
|
|
23
|
+
export function chatHasEffects(name, app = appStore.get()) {
|
|
24
|
+
const v = name ? app.instances?.find(i => i.name === name)?.effect_chat_v : undefined;
|
|
25
|
+
return typeof v === "string" && /^[a-f0-9]{32}$/.test(v) && v !== EMPTY_CHAT_EFFECTS;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The running effects owner, or null while dormant (or when it could not start: as getAppEffects always was). */
|
|
29
|
+
export function getAppEffects(host) { return mod ? mod.getAppEffects(host) : null; }
|
|
30
|
+
|
|
31
|
+
/** Whether the runtime has been asked to start on this page. */
|
|
32
|
+
export function effectsAwake() { return waking !== null; }
|
|
33
|
+
/** Whether its module has loaded (started, or found it could not: getAppEffects() then says which). */
|
|
34
|
+
export function effectsLoaded() { return mod !== null; }
|
|
35
|
+
|
|
36
|
+
/** Called once the runtime has started (or immediately when it already has). Returns the unsubscribe. */
|
|
37
|
+
export function onEffectsAwake(fn) {
|
|
38
|
+
if (typeof fn !== "function") return () => {};
|
|
39
|
+
if (mod) { try { fn(); } catch { /* a listener's error is its own */ } return () => {}; }
|
|
40
|
+
awakeListeners.add(fn); return () => awakeListeners.delete(fn);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Load and start the runtime (once per page). Resolves to the owner, or null when it could not start. */
|
|
44
|
+
export function wakeEffects() {
|
|
45
|
+
if (waking) return waking;
|
|
46
|
+
waking = import("./app-effects.js").then(m => {
|
|
47
|
+
mod = m;
|
|
48
|
+
const owner = chatStore ? m.startAppEffects({ store: chatStore }) : null;
|
|
49
|
+
for (const fn of [...awakeListeners]) { try { fn(); } catch { /* the rest still hear */ } }
|
|
50
|
+
awakeListeners.clear();
|
|
51
|
+
return owner;
|
|
52
|
+
}, () => null);
|
|
53
|
+
return waking;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Wake when the open chat has placements or the person has a personal effect. */
|
|
57
|
+
function check() {
|
|
58
|
+
if (waking) return;
|
|
59
|
+
const route = navStore.get().route;
|
|
60
|
+
if ((route?.panel === "chat" && chatHasEffects(route.instance)) || ownerEffectsChosen()) void wakeEffects();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Boot (once per page, with the Chat store the runtime binds to). Starts nothing but the watch. */
|
|
64
|
+
export function startEffectsWaker({ store }) {
|
|
65
|
+
chatStore = store;
|
|
66
|
+
if (started) return;
|
|
67
|
+
started = true;
|
|
68
|
+
appStore.subscribe(check); navStore.subscribe(check); onOwnerEffectsChanged(check);
|
|
69
|
+
check();
|
|
70
|
+
}
|