@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/shared/app.js
CHANGED
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
// #1665 first: the host's preferences go into this browser's cache before any module below reads its key.
|
|
9
9
|
import { bootPrefs } from "./app-prefs.js";
|
|
10
10
|
import { html, render } from "./app-html.js";
|
|
11
|
-
import { Shell, handleKey, showDialog, closeDrawer, openDrawer, shellStore, retryable, setNewInstanceOpener } from "./app-shell.js";
|
|
11
|
+
import { Shell, handleKey, handleSearchKey, showDialog, closeDrawer, openDrawer, shellStore, retryable, setNewInstanceOpener } from "./app-shell.js";
|
|
12
12
|
import { installSwipe } from "./app-swipe.js";
|
|
13
13
|
import { startRouter, navStore, navigate } from "./app-nav.js";
|
|
14
14
|
import { createStream } from "./app-stream.js";
|
|
15
|
-
import { appStore, applyStatus, applyActivity } from "./app-store.js";
|
|
15
|
+
import { appStore, applyStatus, applyActivity, guestPage } from "./app-store.js";
|
|
16
16
|
import { chatPath } from "./app-route.js";
|
|
17
17
|
import { startNeedsNotifier } from "./app-needs.js";
|
|
18
18
|
import { toast } from "./ui-toast.js";
|
|
@@ -29,6 +29,9 @@ initUsageRail();
|
|
|
29
29
|
// session-only panel, nothing under /ui/ is ever imported or read (#1408 §3).
|
|
30
30
|
const mode = boot.mode === "view-only" ? "view-only" : "full";
|
|
31
31
|
appStore.set({ viewOnly: mode === "view-only" });
|
|
32
|
+
// #1920 phase 3: a guest's page has its chats and nothing else — no panel, notifier, theme list or owner CSS a guest
|
|
33
|
+
// would be refused.
|
|
34
|
+
const guest = guestPage();
|
|
32
35
|
|
|
33
36
|
// A failed load can be retried (the panel's Retry): each retry asks for a fresh URL, since a browser may keep a failed
|
|
34
37
|
// module import cached under the first one. boot() runs once, whichever attempt succeeds.
|
|
@@ -80,19 +83,24 @@ if (mode === "full") {
|
|
|
80
83
|
// a boot import that failed before the Outlet first asked would be retried at once, depending on timing.)
|
|
81
84
|
let chatLoads = 0;
|
|
82
85
|
panels.set("chat", { load: () => (chatLoads++ === 0 ? chatBoot : loadChat()).then((m) => m.ChatPanel) });
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
if (!guest) {
|
|
87
|
+
panels.set("fleet", { load: () => loadFleet().then((m) => m.FleetPanel) });
|
|
88
|
+
panels.set("settings", { load: () => loadSettings().then((m) => m.SettingsPanel) });
|
|
89
|
+
panels.set("needs", { load: () => loadNeeds().then((m) => m.NeedsPanel) });
|
|
90
|
+
// #1523 N2: one instance's Details (/ui/fleet/agent/<name>).
|
|
91
|
+
panels.set("details", { load: () => loadDetails().then((m) => m.DetailsPanel) });
|
|
92
|
+
// #1218: one instance's Log (/ui/log/<name>).
|
|
93
|
+
panels.set("log", { load: () => loadLog().then((m) => m.LogPanel) });
|
|
94
|
+
// #1386 §6.3: desktop notifications for new "Needs you" items, in any panel, while this signed-in page is open.
|
|
95
|
+
startNeedsNotifier({ open: (instance) => navigate(chatPath(instance)) });
|
|
96
|
+
// #2233: a backend sign-in running anywhere is shown on every admin page (the module lives behind the session gate).
|
|
97
|
+
import("/ui/js/backend-signin.js").then((m) => m.installSignIn(), () => {});
|
|
98
|
+
}
|
|
92
99
|
// #1665: this device's first upload of its own choices (once), then another device's changes at each status frame.
|
|
100
|
+
// A guest's page only reads them (prefsWritable is false there).
|
|
93
101
|
bootPrefs();
|
|
94
|
-
// #1629: agents' themes, read when the status frame's themes_rev moves.
|
|
95
|
-
startThemes();
|
|
102
|
+
// #1629: agents' themes, read when the status frame's themes_rev moves (a guest's frames never carry it).
|
|
103
|
+
if (!guest) startThemes();
|
|
96
104
|
onNewInstance = async () => {
|
|
97
105
|
closeDrawer(); // on a phone the dialog opens from the drawer: the drawer goes first
|
|
98
106
|
let m;
|
|
@@ -103,8 +111,9 @@ if (mode === "full") {
|
|
|
103
111
|
} else appStore.set({ connection: "none" });
|
|
104
112
|
|
|
105
113
|
startRouter(window);
|
|
106
|
-
// #1629 r4 §3: the person's owner CSS (full app only; never on Settings, in safe mode or where this device turned it off
|
|
107
|
-
|
|
114
|
+
// #1629 r4 §3: the person's owner CSS (full app only; never on Settings, in safe mode or where this device turned it off;
|
|
115
|
+
// never a guest's — owner CSS is an admin's).
|
|
116
|
+
if (!guest) installOwnerCss();
|
|
108
117
|
// /ui on its own opens the chat this browser had open last (an unknown name shows the panel's "not found").
|
|
109
118
|
{
|
|
110
119
|
const r = navStore.get().route;
|
|
@@ -114,6 +123,8 @@ installOwnerCss();
|
|
|
114
123
|
}
|
|
115
124
|
|
|
116
125
|
document.addEventListener("keydown", handleKey);
|
|
126
|
+
// #2293: Ctrl+K / ⌘K (and Ctrl+F in a chat) — an admin's full app only; the server refuses a guest's search anyway.
|
|
127
|
+
if (mode === "full" && !guest) document.addEventListener("keydown", handleSearchKey);
|
|
117
128
|
// #1605: on a phone (the sidebar is a drawer), swipe right in the main area to open it, left to close it — the same
|
|
118
129
|
// open/close as ☰ and the scrim. Nothing that owns a sideways drag (code, tables, the terminal, inputs, dialogs) and
|
|
119
130
|
// never the left edge (iOS: "back").
|
|
@@ -11,8 +11,12 @@ register("card", {
|
|
|
11
11
|
sizeSName: "Small", sizeMName: "Medium", sizeLName: "Large",
|
|
12
12
|
byHuman: "set by a person",
|
|
13
13
|
byAgent: "set by the agent",
|
|
14
|
+
menu: "Identity card",
|
|
14
15
|
show: "Show card",
|
|
15
16
|
hide: "Hide card",
|
|
17
|
+
showRow: "Show picture and bio",
|
|
18
|
+
bannerSafe: "Every screen",
|
|
19
|
+
bannerPhone: "Phone · M card",
|
|
16
20
|
},
|
|
17
21
|
"zh-TW": {
|
|
18
22
|
label: "{0} 的身分卡",
|
|
@@ -22,7 +26,11 @@ register("card", {
|
|
|
22
26
|
sizeSName: "小", sizeMName: "中", sizeLName: "大",
|
|
23
27
|
byHuman: "由人設定",
|
|
24
28
|
byAgent: "由 agent 設定",
|
|
29
|
+
menu: "身分卡",
|
|
25
30
|
show: "顯示卡片",
|
|
26
31
|
hide: "隱藏卡片",
|
|
32
|
+
showRow: "顯示小圖和介紹",
|
|
33
|
+
bannerSafe: "所有螢幕",
|
|
34
|
+
bannerPhone: "手機 · 中卡片",
|
|
27
35
|
},
|
|
28
36
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** #1629: app-owned pool; Chat contributes a lease, never another registry/loader. */
|
|
2
|
+
import { effectPlaybackReason, EFFECT_BLOCK_REASONS } from "./effect-policy.js";
|
|
2
3
|
import { createEffectTrialSource } from "./effect-trial-source.js";
|
|
3
4
|
import { createOwnerEffectSource } from "./effect-owner-source.js";
|
|
4
5
|
import { createEffectRuntimeLoader } from "./effect-runtime-loader.js";
|
|
@@ -6,7 +7,7 @@ import { createEffectRegistry } from "./effect-registry.js";
|
|
|
6
7
|
import { createEffectSafety } from "./effect-safety.js";
|
|
7
8
|
import { stockEffectTrust } from "./effect-stock-trust.js";
|
|
8
9
|
import { createEffectProfileCollector } from "./effect-profiles.js";
|
|
9
|
-
import {
|
|
10
|
+
import { effectPlaybackIdentity, createEffectWriter } from "./effect-chat-runtime.js";
|
|
10
11
|
|
|
11
12
|
const episodes = new WeakSet();
|
|
12
13
|
|
|
@@ -14,7 +15,8 @@ const episodes = new WeakSet();
|
|
|
14
15
|
export function createAppEffectRuntime({ window: host, current, observe, onChange = () => {}, onDispose = () => {}, resolveOwnerTarget = null, onOwnerUnavailable = () => {}, factories = {} }) {
|
|
15
16
|
let dead = false, error = null, loader = null, registry = null, writer = null, source = null, personal = null, trial = null, pending = null, lastMetadata = null, serial = 0, revision = 0, stamp = null, syncing = false, queued = false;
|
|
16
17
|
const bindings = new Map(), profiles = new Map();
|
|
17
|
-
|
|
18
|
+
let lastContext = null, playbackUnavailable = null;
|
|
19
|
+
const identity = effectPlaybackIdentity(host), auth = host?.AgendAuth;
|
|
18
20
|
const safe = fn => { try { return fn(); } catch { return null; } };
|
|
19
21
|
const alive = () => { if (dead) return false; const value = safe(() => current() === true && !auth.sessionEnded()); return !dead && value === true; };
|
|
20
22
|
const sourceAlive = s => {
|
|
@@ -25,15 +27,49 @@ export function createAppEffectRuntime({ window: host, current, observe, onChang
|
|
|
25
27
|
const placementIdentity = p => JSON.stringify([p.by, p.manifest.layer, p.params]);
|
|
26
28
|
const clock = () => host.performance.now();
|
|
27
29
|
let offAuth = null;
|
|
28
|
-
const snapshot = s =>
|
|
30
|
+
const snapshot = s => {
|
|
31
|
+
const effects = s?.metadata?.effects ?? [], known = registry?.snapshot?.() ?? [];
|
|
32
|
+
const rows = effects.map(p => {
|
|
33
|
+
const b = s.bindings.get(p.id), row = known.find(r => b && r.key === b.key && r.id === p.id);
|
|
34
|
+
if (row && !s.error && !playbackUnavailable) return row.reason == null && row.phase == null ? { ...row, phase: "loading" } : row;
|
|
35
|
+
return { id: p.id, key: b?.key ?? null, verdict: "off", phase: null, quality: null,
|
|
36
|
+
reason: s.error ?? playbackUnavailable ?? effectPlaybackReason(lastContext) ?? (!p.manifest.regions.includes("chat") ? "region_unavailable" : "runtime_unavailable") };
|
|
37
|
+
});
|
|
38
|
+
return { error: error ?? s?.error ?? null, rows, effects, loading: !!s && !s.dead && s.metadata === null && !s.error };
|
|
39
|
+
};
|
|
29
40
|
function tell() {
|
|
30
41
|
const s = source, value = snapshot(s); if (dead) return;
|
|
31
|
-
if (sourceAlive(s))
|
|
42
|
+
if (sourceAlive(s)) {
|
|
43
|
+
const next = JSON.stringify(value);
|
|
44
|
+
if (next !== s.lastSnapshot) { s.lastSnapshot = next; safe(() => s.onChange(value)); }
|
|
45
|
+
}
|
|
32
46
|
if (!dead) safe(() => onChange(snapshot(source)));
|
|
33
47
|
}
|
|
48
|
+
function reportPlayback(s) {
|
|
49
|
+
if (!sourceAlive(s) || identity.scope !== "owner") return;
|
|
50
|
+
const now = safe(clock);
|
|
51
|
+
if (!Number.isFinite(now) || now < 0 || s.reportClock !== null && now < s.reportClock) { s.reporter.dispose(); return; }
|
|
52
|
+
s.reportClock = now;
|
|
53
|
+
const value = snapshot(s);
|
|
54
|
+
for (const id of s.reported.keys()) if (!value.effects.some(p => p.id === id)) s.reported.delete(id);
|
|
55
|
+
for (const row of value.rows) {
|
|
56
|
+
if (!sourceAlive(s)) return;
|
|
57
|
+
const status = row.phase === "running" && row.reason == null ? "playing" : "blocked";
|
|
58
|
+
const reason = status === "playing" ? null : EFFECT_BLOCK_REASONS.includes(row.reason) ? row.reason : "unknown";
|
|
59
|
+
const device = lastContext?.constrained === true ? "constrained" : "standard";
|
|
60
|
+
const next = JSON.stringify([status, reason, device]), last = s.reported.get(row.id);
|
|
61
|
+
if (last && (now - last.at < 10000 || next === last.value && now - last.at < 60000)) continue;
|
|
62
|
+
s.reported.set(row.id, { at: now, value: next });
|
|
63
|
+
void s.reporter.post(`/ui/effects/profile/${row.id}?instance=${encodeURIComponent(s.name)}&ownerEpoch=${identity.epoch}`,
|
|
64
|
+
{ v: 1, kind: "playback", device, status, reason });
|
|
65
|
+
}
|
|
66
|
+
// No server polling or unbounded retries: at most one observation per effect / 10s, a 60s heartbeat.
|
|
67
|
+
if (sourceAlive(s)) s.reportTimer = host.setTimeout(() => reportPlayback(s), 10000);
|
|
68
|
+
}
|
|
34
69
|
function discard(s) {
|
|
35
70
|
if (!s || s.dead) return;
|
|
36
71
|
s.dead = true; s.generation++; s.metadata = null;
|
|
72
|
+
safe(() => host.clearTimeout(s.reportTimer)); safe(() => s.reporter?.dispose());
|
|
37
73
|
if (source === s) source = null;
|
|
38
74
|
safe(() => trial?.stopFor(s));
|
|
39
75
|
for (const b of s.bindings.values()) bindings.delete(b.key); s.bindings.clear();
|
|
@@ -63,7 +99,7 @@ export function createAppEffectRuntime({ window: host, current, observe, onChang
|
|
|
63
99
|
writer = createEffectWriter(host, alive);
|
|
64
100
|
loader = make("loader", createEffectRuntimeLoader)({ window: host, ownerEpoch: identity.epoch, current: alive, onRevoke: () => fail("runtime_unavailable") });
|
|
65
101
|
if (!loader.ok || typeof loader.placementsSettled !== "function" || typeof loader.resourceSettled !== "function") throw Error("runtime_unavailable");
|
|
66
|
-
registry = make("registry", createEffectRegistry)({ window: host, origin: host.
|
|
102
|
+
registry = make("registry", createEffectRegistry)({ window: host, origin: host.location?.origin, boot: host.document.body.dataset.effectBoot,
|
|
67
103
|
load: async (descriptor, signal) => {
|
|
68
104
|
const b = bindingFor(descriptor.key, descriptor.id);
|
|
69
105
|
if (!bindingAlive(b) || descriptor.id !== b.id || signal.aborted) return null;
|
|
@@ -78,7 +114,7 @@ export function createAppEffectRuntime({ window: host, current, observe, onChang
|
|
|
78
114
|
stockSnapshot: ({ frameRoot }) => make("stock", stockEffectTrust)(host, { frameRoot, ownerCss: observe().ownerCss }),
|
|
79
115
|
onChange() { try { tell(); emit(); } catch { fail("runtime_unavailable"); } },
|
|
80
116
|
onMount(owner) {
|
|
81
|
-
const b = bindingFor(owner.key, owner.id); if (!bindingAlive(b) || b.id !== owner.id || !owner.current()) return;
|
|
117
|
+
const b = bindingFor(owner.key, owner.id); if (identity.scope !== "owner" || !bindingAlive(b) || b.id !== owner.id || !owner.current()) return;
|
|
82
118
|
const collector = make("collector", createEffectProfileCollector)({ key: owner.key, id: owner.id, device: owner.device, clock });
|
|
83
119
|
const began = safe(clock);
|
|
84
120
|
if (collector.ok && Number.isFinite(began) && began >= 0 && bindingAlive(b) && owner.current()) profiles.set(owner, { binding: b, collector, began, attempted: null }); else collector.dispose?.();
|
|
@@ -95,17 +131,23 @@ export function createAppEffectRuntime({ window: host, current, observe, onChang
|
|
|
95
131
|
void writer.post(`/ui/effects/profile/${owner.id}?${query}ownerEpoch=${identity.epoch}`, report.profile);
|
|
96
132
|
}, clock,
|
|
97
133
|
});
|
|
98
|
-
if (!registry.ok)
|
|
99
|
-
|
|
134
|
+
if (!registry.ok) {
|
|
135
|
+
// Keep authenticated placement metadata available for explanations. This inert adapter cannot create,
|
|
136
|
+
// load, resume, send to, or authorize a frame; a failed paint subsystem is never permission.
|
|
137
|
+
playbackUnavailable = "unsupported_host";
|
|
138
|
+
safe(() => registry?.dispose?.());
|
|
139
|
+
registry = { ok: true, frameRoot: null, update() {}, snapshot: () => [], publish() {}, resume: () => false, dispose() {} };
|
|
140
|
+
}
|
|
141
|
+
if (identity.scope === "owner") trial = make("trialSource", createEffectTrialSource)({ window: host, current: () => {
|
|
100
142
|
if (!alive()) return false; const v = observe(), c = v.context;
|
|
101
|
-
return alive() &&
|
|
102
|
-
&& c.safe === false && c.publicLink === false && c.reducedMotion === false && c.
|
|
143
|
+
return alive() && c.enabled === true && c.show === true && c.transportReady === true
|
|
144
|
+
&& c.safe === false && c.publicLink === false && c.reducedMotion === false && c.hidden === false;
|
|
103
145
|
}, ownerCurrent: (s, name) => sourceAlive(s) && s.name === name,
|
|
104
146
|
stock: () => registry.frameRoot ? make("stock", stockEffectTrust)(host, { frameRoot: registry.frameRoot, ownerCss: observe().ownerCss }) : null,
|
|
105
147
|
readResource: (spec, signal) => typeof loader.trialResourceSettled === "function" ? loader.trialResourceSettled(spec, signal) : Promise.resolve(null),
|
|
106
148
|
onChange() { revision++; stamp = null; sync(); tell(); },
|
|
107
149
|
});
|
|
108
|
-
if (typeof resolveOwnerTarget === "function") {
|
|
150
|
+
if (identity.scope === "owner" && typeof resolveOwnerTarget === "function") {
|
|
109
151
|
if (typeof loader.ownerSettled !== "function") throw Error("runtime_unavailable");
|
|
110
152
|
personal = createOwnerEffectSource({ current: alive,
|
|
111
153
|
readMetadata: signal => loader.ownerSettled(signal), readResource: (spec, signal) => loader.resourceSettled(spec, signal), resolveTarget: resolveOwnerTarget,
|
|
@@ -139,7 +181,7 @@ export function createAppEffectRuntime({ window: host, current, observe, onChang
|
|
|
139
181
|
}
|
|
140
182
|
function refuseSource(s) {
|
|
141
183
|
if (!sourceAlive(s)) return;
|
|
142
|
-
s.error = "resource_unavailable"; s.
|
|
184
|
+
s.error = "resource_unavailable"; s.generation++;
|
|
143
185
|
for (const b of s.bindings.values()) bindings.delete(b.key); s.bindings.clear(); revision++; stamp = null; sync(); tell();
|
|
144
186
|
}
|
|
145
187
|
function queueSync() {
|
|
@@ -156,6 +198,7 @@ export function createAppEffectRuntime({ window: host, current, observe, onChang
|
|
|
156
198
|
if (!alive() || revision !== before || s !== source) { if (alive()) queueSync(); return; }
|
|
157
199
|
const entries = [...(personal?.descriptors() ?? []), ...(trial?.descriptors() ?? []), ...descriptors(s)];
|
|
158
200
|
if (!alive() || revision !== before || s !== source) { stamp = null; if (alive()) queueSync(); return; }
|
|
201
|
+
lastContext = { ...value.context };
|
|
159
202
|
const next = JSON.stringify([revision, value.context, entries.map(d => [d.key, d.paused])]);
|
|
160
203
|
if (stamp !== next) {
|
|
161
204
|
if (!alive() || revision !== before || s !== source) { stamp = null; if (alive()) queueSync(); return; }
|
|
@@ -164,12 +207,15 @@ export function createAppEffectRuntime({ window: host, current, observe, onChang
|
|
|
164
207
|
if (!alive() || revision !== before || s !== source) { stamp = null; if (alive()) queueSync(); return; }
|
|
165
208
|
emit();
|
|
166
209
|
const c = value.context;
|
|
167
|
-
|
|
168
|
-
|
|
210
|
+
// Metadata describes placements, not executable resources. Its existing session/epoch/current and
|
|
211
|
+
// shared settled-lane fences apply even when this viewer has declined playback. No code is fetched here.
|
|
212
|
+
if (!pending) {
|
|
213
|
+
const ownerNeeds = identity.scope === "owner" && !c.publicLink && c.enabled && c.show && c.transportReady && !c.safe && !c.reducedMotion && !c.hidden && personal?.needsMetadata() === true, chatNeeds = sourceAlive(s) && !s.error && !s.metadata;
|
|
169
214
|
if (!alive() || revision !== before || s !== source) { if (alive()) queueSync(); return; }
|
|
170
215
|
if (ownerNeeds && (!chatNeeds || lastMetadata !== "owner")) void readPersonal();
|
|
171
216
|
else if (chatNeeds) void read(s);
|
|
172
217
|
}
|
|
218
|
+
tell();
|
|
173
219
|
} catch { fail("runtime_unavailable"); }
|
|
174
220
|
finally { syncing = false; }
|
|
175
221
|
}
|
|
@@ -216,14 +262,17 @@ export function createAppEffectRuntime({ window: host, current, observe, onChang
|
|
|
216
262
|
for (const b of s.bindings.values()) bindings.delete(b.key); s.bindings.clear();
|
|
217
263
|
if (pending?.source === s) safe(() => pending.controller.abort()); revision++; stamp = null; sync(); tell();
|
|
218
264
|
}
|
|
219
|
-
function publishReply(s, event) { if (sourceAlive(s) && !s.error && event.instance === s.name) registry.publish("reply", {}, s.name); }
|
|
265
|
+
function publishReply(s, event) { if (host.document.body.dataset.webTransport !== "poll" && sourceAlive(s) && !s.error && event.instance === s.name) registry.publish("reply", {}, s.name); }
|
|
220
266
|
function attachChat(options) {
|
|
221
267
|
discard(source); const expected = revision; sync();
|
|
222
268
|
if (!alive() || revision !== expected || source !== null) return null;
|
|
223
269
|
const { name, element, current: ownCurrent, observe: ownObserve, onChange: ownChange = () => {} } = options ?? {};
|
|
224
270
|
if (!alive() || typeof name !== "string" || name.length < 1 || name.length > 128 || !element || element.isConnected !== true || typeof ownCurrent !== "function" || typeof ownObserve !== "function" || safe(() => ownCurrent() === true) !== true || revision !== expected || source !== null) return null;
|
|
225
271
|
const s = { name, element, current: ownCurrent, observe: ownObserve, onChange: ownChange, serial: ++serial, generation: 0, dead: false, error: null, metadata: null, bindings: new Map(), paused: new Map() };
|
|
226
|
-
source = s;
|
|
272
|
+
source = s;
|
|
273
|
+
s.reporter = createEffectWriter(host, () => sourceAlive(s)); s.reported = new Map(); s.reportClock = null;
|
|
274
|
+
s.reportTimer = host.setTimeout(() => reportPlayback(s), 10000);
|
|
275
|
+
revision++; stamp = null;
|
|
227
276
|
const placement = id => sourceAlive(s) && !s.error ? s.metadata?.effects.find(p => p.id === id) : null;
|
|
228
277
|
const changed = () => changedSource(s);
|
|
229
278
|
const api = { ok: true, sync,
|
|
@@ -231,14 +280,14 @@ export function createAppEffectRuntime({ window: host, current, observe, onChang
|
|
|
231
280
|
reply(event) { publishReply(s, event); },
|
|
232
281
|
pause(id) { const p = placement(id); if (!p) return false; s.paused.set(id, placementIdentity(p)); revision++; sync(); return true; },
|
|
233
282
|
resume(id) { const p = placement(id); if (!p) return false; s.paused.delete(id); revision++; sync(); const b = s.bindings.get(id); return bindingAlive(b) && registry.resume(b.key); },
|
|
234
|
-
async remove(id) { const p = placement(id); if (!p) return false; const generation = s.generation; const ok = await writer.post("/ui/effects/remove", { instance: s.name, id }); if (!sourceAlive(s) || generation !== s.generation) return false; if (ok) changed(); return ok; },
|
|
283
|
+
async remove(id) { const p = placement(id); if (identity.scope !== "owner" || !p) return false; const generation = s.generation; const ok = await writer.post("/ui/effects/remove", { instance: s.name, id }); if (!sourceAlive(s) || generation !== s.generation) return false; if (ok) changed(); return ok; },
|
|
235
284
|
snapshot: () => sourceAlive(s) ? snapshot(s) : { error: null, rows: [], effects: [], loading: false },
|
|
236
285
|
dispose() { if (source !== s) return; discard(s); sync(); },
|
|
237
286
|
};
|
|
238
287
|
sync(); tell(); return sourceAlive(s) ? api : null;
|
|
239
288
|
}
|
|
240
|
-
return { ok: true, sync, attachChat, changedOwner, ownerSnapshot,
|
|
241
|
-
attachTrial(options) { const s = source; return sourceAlive(s) ? trial.attach(options, s) : null; },
|
|
289
|
+
return { ok: true, playbackAvailable: playbackUnavailable === null, sync, attachChat, changedOwner, ownerSnapshot,
|
|
290
|
+
attachTrial(options) { const s = source; return trial && !playbackUnavailable && sourceAlive(s) ? trial.attach(options, s) : null; },
|
|
242
291
|
stopTrial() { trial?.stop(); },
|
|
243
292
|
trialSnapshot() { return { rows: registry?.snapshot().filter(row => trial?.binding(row.key, row.id)) ?? [] }; },
|
|
244
293
|
cursorSelection: () => personal?.cursorSelection() ?? null, cursorImageSettled,
|
|
@@ -46,10 +46,23 @@ export function createEffectWriter(host, current) {
|
|
|
46
46
|
export function chatEffectIdentity(host) {
|
|
47
47
|
try {
|
|
48
48
|
const b = host.document.body.dataset, x = JSON.parse(host.document.documentElement.getAttribute("data-prefs") || "null");
|
|
49
|
-
if (b.mode !== "full" || b.publicLink === "1" || b.webTransport === "poll" || !["person", "operator"].includes(x?.ownerScope) || x.unreadable === true || typeof x.ownerEpoch !== "string" || !/^[a-f0-9]{16}$/.test(x.ownerEpoch)) return null;
|
|
49
|
+
if (b.mode !== "full" || b.tier === "guest" || b.publicLink === "1" || b.webTransport === "poll" || !["person", "operator"].includes(x?.ownerScope) || x.unreadable === true || typeof x.ownerEpoch !== "string" || !/^[a-f0-9]{16}$/.test(x.ownerEpoch)) return null;
|
|
50
50
|
return Object.freeze({ epoch: x.ownerEpoch, scope: x.ownerScope });
|
|
51
51
|
} catch { return null; }
|
|
52
52
|
}
|
|
53
|
+
/** Chat-only public/guest capability stays separate from personal preferences and management. */
|
|
54
|
+
export function effectPlaybackIdentity(host) {
|
|
55
|
+
try {
|
|
56
|
+
const b = host.document.body.dataset;
|
|
57
|
+
if (b.mode !== "full" || typeof b.effectEpoch !== "string" || !/^[a-f0-9]{16}$/.test(b.effectEpoch)) return null;
|
|
58
|
+
if (b.effectScope === "owner") {
|
|
59
|
+
const own = chatEffectIdentity(host);
|
|
60
|
+
return own?.epoch === b.effectEpoch ? Object.freeze({ epoch: b.effectEpoch, scope: "owner" }) : null;
|
|
61
|
+
}
|
|
62
|
+
if (b.effectScope === "chat" && (b.publicLink === "1" && b.webTransport === "poll" || b.tier === "guest")) return Object.freeze({ epoch: b.effectEpoch, scope: "chat" });
|
|
63
|
+
} catch { /* no session capability */ }
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
53
66
|
export function chatEffectState(state, name) {
|
|
54
67
|
const inst = state.instances?.find(i => i.name === name);
|
|
55
68
|
if (!inst) return null;
|
|
@@ -81,7 +94,7 @@ export function createChatEffectRuntime({ window: host, name, element, current,
|
|
|
81
94
|
writer = createEffectWriter(host, alive);
|
|
82
95
|
loader = make("loader", createEffectRuntimeLoader)({ window: host, ownerEpoch: identity.epoch, current: alive });
|
|
83
96
|
if (!loader.ok) throw Error("runtime_unavailable");
|
|
84
|
-
registry = make("registry", createEffectRegistry)({ window: host, origin: host.
|
|
97
|
+
registry = make("registry", createEffectRegistry)({ window: host, origin: host.location?.origin, boot: host.document.body.dataset.effectBoot,
|
|
85
98
|
load: async (d, signal) => {
|
|
86
99
|
const mine = generation, value = await loader.resource({ id: d.id, instance: name }, signal);
|
|
87
100
|
if (!alive() || mine !== generation || signal.aborted) return null;
|
|
@@ -125,7 +138,7 @@ export function createChatEffectRuntime({ window: host, name, element, current,
|
|
|
125
138
|
const stamp = JSON.stringify(ctx);
|
|
126
139
|
if (stamp !== lastContext) { lastContext = stamp; registry.update(descriptors(), ctx); }
|
|
127
140
|
emit();
|
|
128
|
-
if (!metadata && !active && ctx.enabled && ctx.show && ctx.
|
|
141
|
+
if (!metadata && !active && ctx.enabled && ctx.show && ctx.transportReady && !ctx.safe && !ctx.publicLink && !ctx.reducedMotion && !ctx.hidden) void read();
|
|
129
142
|
}
|
|
130
143
|
async function read() {
|
|
131
144
|
if (!alive() || error || active) return;
|
|
@@ -30,16 +30,35 @@ function snapshotRender(raw) {
|
|
|
30
30
|
return { code: raw.code, params: { ...params }, instance: raw.instance, width: raw.width, height: raw.height, dpr: raw.dpr, assets, wants: [...raw.wants] };
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/** Listener transport only; this does not grant HTML-preview or ordinary-effect consent. */
|
|
34
|
+
export function effectTransportReady(host, origin, boot) {
|
|
35
|
+
try {
|
|
36
|
+
const target = new URL(origin), page = new URL(host.location.origin);
|
|
37
|
+
return origin === target.origin && ["http:", "https:"].includes(target.protocol) && ["http:", "https:"].includes(page.protocol)
|
|
38
|
+
&& target.origin !== page.origin && !(page.protocol === "https:" && target.protocol !== "https:") && typeof boot === "string" && /^[0-9a-f]{32}$/.test(boot);
|
|
39
|
+
} catch { return false; }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** #2118: same URL origin, opaque realm. This helper does not alter product admission or HTML preview consent. */
|
|
43
|
+
export function effectBootstrapReady(host, origin, boot) {
|
|
44
|
+
try {
|
|
45
|
+
const target = new URL(origin), page = new URL(host.location.origin);
|
|
46
|
+
return origin === target.origin && target.origin === page.origin && ["http:", "https:"].includes(target.protocol)
|
|
47
|
+
&& typeof boot === "string" && /^[0-9a-f]{32}$/.test(boot);
|
|
48
|
+
} catch { return false; }
|
|
49
|
+
}
|
|
50
|
+
|
|
33
51
|
/**
|
|
34
52
|
* All options come from the host, never from an agent event or manifest origin. The registry supplies an admitted
|
|
35
53
|
* root and geometry in a later integration. This primitive alone does not implement caps, clipping or profiling.
|
|
36
54
|
*/
|
|
37
|
-
export function createEffectFrame(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
55
|
+
export function createEffectFrame(options = {}) { return ownedEffectFrame(options, false); }
|
|
56
|
+
|
|
57
|
+
/** Product transport: the same-listener trusted document still has an opaque sandboxed realm. */
|
|
58
|
+
export function createOpaqueEffectFrame(options = {}) { return ownedEffectFrame(options, true); }
|
|
59
|
+
|
|
60
|
+
function ownedEffectFrame({ window: host, root, origin, boot, render, clock = () => performance.now(), onReady = () => {}, onRemove = () => {} } = {}, sameListener) {
|
|
61
|
+
if (!(sameListener ? effectBootstrapReady : effectTransportReady)(host, origin, boot)) return { ok: false, reason: sameListener ? "bootstrap_unknown" : "preview_unknown" };
|
|
43
62
|
const payload = snapshotRender(render);
|
|
44
63
|
if (!payload || !host || !root || root.ownerDocument !== host.document || typeof root.append !== "function" || typeof host.MessageChannel !== "function" || typeof clock !== "function" || typeof onReady !== "function" || typeof onRemove !== "function") return { ok: false, reason: "unsupported" };
|
|
45
64
|
let mounted;
|
|
@@ -117,7 +136,7 @@ export function createEffectFrame({ window: host, root, origin, boot, render, cl
|
|
|
117
136
|
host.addEventListener("message", hello);
|
|
118
137
|
frame.addEventListener("load", load);
|
|
119
138
|
timer = host.setTimeout(deadline, 10000);
|
|
120
|
-
try { frame.src =
|
|
139
|
+
try { frame.src = origin + (sameListener ? "/effect-bootstrap" : "/effect"); root.append(frame); } catch { remove("mount_failed"); }
|
|
121
140
|
return { ok: true, element: frame, state, remove,
|
|
122
141
|
send(event) {
|
|
123
142
|
if (!ready || !current() || !maySendEffectEvent(event, { instance: payload.instance, wants: payload.wants })) return false;
|
|
@@ -70,36 +70,51 @@ export function createEffectGeometry({ window: host } = {}) {
|
|
|
70
70
|
frame.remove();
|
|
71
71
|
return { ok: false, reason: "geometry_unknown" };
|
|
72
72
|
}
|
|
73
|
-
let dead = false;
|
|
73
|
+
let dead = false, shown = false;
|
|
74
74
|
const hide = () => {
|
|
75
|
+
shown = false;
|
|
75
76
|
try {
|
|
76
77
|
rule.style.setProperty("visibility", "hidden", "important");
|
|
77
|
-
if (frame.isConnected === true && host.getComputedStyle(frame).visibility !== "hidden") frame.remove();
|
|
78
|
-
|
|
78
|
+
if (frame.isConnected === true && host.getComputedStyle(frame).visibility !== "hidden") { frame.remove(); return false; }
|
|
79
|
+
return true;
|
|
80
|
+
} catch { frame.remove(); return false; }
|
|
79
81
|
};
|
|
80
82
|
const handle = { ok: true, hide,
|
|
81
83
|
/** Same-turn host paint snapshot is mandatory, including behind/human placements. No DOM fallback. */
|
|
82
84
|
sync(rect, stock) {
|
|
83
|
-
hide();
|
|
85
|
+
const refuse = () => { hide(); return false; };
|
|
84
86
|
const proof = effectStockSnapshot(stock);
|
|
85
|
-
if (!effectStockAllows(proof, { layer, by })) return
|
|
86
|
-
if (dead || disposed || !hostEffectSpace(host, frame) || !rect || ![rect.x, rect.y, rect.width, rect.height].every(number) || ![rect.x, rect.y, rect.width, rect.height].every(Number.isInteger) || rect.width < 1 || rect.height < 1 || rect.width > 4096 || rect.height > 4096 || rect.width * rect.height > 8388608)
|
|
87
|
+
if (!effectStockAllows(proof, { layer, by })) return refuse();
|
|
88
|
+
if (dead || disposed || !hostEffectSpace(host, frame) || !rect || ![rect.x, rect.y, rect.width, rect.height].every(number) || ![rect.x, rect.y, rect.width, rect.height].every(Number.isInteger) || rect.width < 1 || rect.height < 1 || rect.width > 4096 || rect.height > 4096 || rect.width * rect.height > 8388608) return refuse();
|
|
87
89
|
let path = "none";
|
|
88
90
|
if (layer === "front" && by !== "human") {
|
|
89
91
|
const clipped = effectTrustClip(rect, proof.rects);
|
|
90
|
-
if (!clipped.ok) return
|
|
92
|
+
if (!clipped.ok) return refuse();
|
|
91
93
|
path = clipped.path;
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
try {
|
|
97
|
+
const matches = visibility => {
|
|
98
|
+
const style = host.getComputedStyle(frame), actual = frame.getBoundingClientRect();
|
|
99
|
+
return style.position === "fixed" && style.pointerEvents === "none" && style.visibility === visibility
|
|
100
|
+
&& style.opacity === (layer === "front" && by !== "human" ? "0.85" : "1") && style.zIndex === (layer === "front" ? "20" : "0")
|
|
101
|
+
&& sameClip(path, rule.style.getPropertyValue("clip-path")) && sameClip(path, style.clipPath)
|
|
102
|
+
&& ["x", "y", "width", "height"].every(key => number(actual[key]) && Math.abs(actual[key] - rect[key]) < 0.02)
|
|
103
|
+
&& frame.isConnected === true && frame.ownerDocument === host.document;
|
|
104
|
+
};
|
|
105
|
+
// A fresh proof and actual viewport/clip are still checked every frame. Rewriting an
|
|
106
|
+
// already verified presentation makes WebKit repeatedly invalidate the iframe's paint.
|
|
107
|
+
if (shown && matches("visible")) return true;
|
|
108
|
+
// Changed or unproven geometry is hidden before writes and verified before reveal.
|
|
109
|
+
if (!hide()) return false;
|
|
95
110
|
for (const key of ["x", "y", "width", "height"]) rule.style.setProperty(key === "x" ? "left" : key === "y" ? "top" : key, `${rect[key]}px`, "important");
|
|
96
111
|
rule.style.setProperty("clip-path", path, "important");
|
|
97
112
|
if (!sameClip(path, rule.style.getPropertyValue("clip-path"))) return false;
|
|
98
113
|
// The stylesheet can lose the cascade to another host rule. Verify the actual viewport and clip
|
|
99
114
|
// before revealing: holes expressed in planned coordinates cannot protect a transformed frame.
|
|
100
|
-
|
|
101
|
-
if (style.position !== "fixed" || style.pointerEvents !== "none" || style.visibility !== "hidden" || style.opacity !== (layer === "front" && by !== "human" ? "0.85" : "1") || style.zIndex !== (layer === "front" ? "20" : "0") || !sameClip(path, style.clipPath) || !["x", "y", "width", "height"].every(key => number(actual[key]) && Math.abs(actual[key] - rect[key]) < 0.02)) return false;
|
|
115
|
+
if (!matches("hidden")) return false;
|
|
102
116
|
rule.style.setProperty("visibility", "visible", "important");
|
|
117
|
+
shown = true;
|
|
103
118
|
return true;
|
|
104
119
|
} catch { hide(); return false; }
|
|
105
120
|
},
|
|
@@ -74,7 +74,14 @@ export function createOwnerEffectSource({ current, readMetadata, readResource, r
|
|
|
74
74
|
},
|
|
75
75
|
descriptors() {
|
|
76
76
|
if (!alive() || !metadata || error) return [];
|
|
77
|
-
const body = metadata, gen = generation
|
|
77
|
+
const body = metadata, gen = generation;
|
|
78
|
+
// #2188: the stock snapshot walks the whole page (thousands of computed styles). With no slot holding an effect
|
|
79
|
+
// that could play, there is nothing for it to admit: skipped — every page change (a status, a chat switch) ran it.
|
|
80
|
+
if (!SLOTS.some(slot => body.slots[slot]?.status === "available" && failed.get(slot) !== body.slots[slot].id)) {
|
|
81
|
+
for (const slot of SLOTS) drop(slot);
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
const proof = safe(() => effectStockSnapshot(stock()));
|
|
78
85
|
if (!fresh(body, gen)) return [];
|
|
79
86
|
if (!proof) { clear(); return []; }
|
|
80
87
|
const result = [];
|
|
@@ -58,23 +58,35 @@ export function constrainedDevice(signals) {
|
|
|
58
58
|
return !!signals && (finite(signals.memory) && signals.memory > 0 && signals.memory <= 4 || finite(signals.cores) && signals.cores > 0 && signals.cores <= 4 || signals.coarse === true && finite(signals.width) && signals.width > 0 && signals.width <= 900);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
export const EFFECT_BLOCK_REASONS = Object.freeze([
|
|
62
|
+
"transport_unavailable", "preview_unavailable", "preview_not_consented", "unsupported_host", "safe_mode", "reduced_motion", "save_data", "hidden", "disabled", "public_link",
|
|
63
|
+
"baseline_unsupported", "baseline_unavailable", "baseline_timeout", "calibrating", "clock_unavailable", "gates_unknown", "owner_css", "stock_overlay", "modal",
|
|
64
|
+
"safety_off", "safety_unknown", "interrupted", "paused", "host_budget", "frame_limit", "front_limit", "offscreen", "region_unavailable", "resource_unavailable", "resource_timeout", "runtime_unavailable", "unknown",
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
/** Diagnostic names only; the admission policy below still validates every required flag. */
|
|
68
|
+
export function effectPlaybackReason(c) {
|
|
69
|
+
if (!c || ["safe", "publicLink", "reducedMotion", "hidden", "enabled", "show", "transportReady"].some(k => typeof c[k] !== "boolean")) return "gates_unknown";
|
|
70
|
+
return c.safe ? "safe_mode" : c.reducedMotion ? "reduced_motion"
|
|
71
|
+
: c.hidden ? "hidden" : !c.enabled ? "unsupported_host" : !c.show ? "disabled" : !c.transportReady ? "transport_unavailable" : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
61
74
|
const OWNER_ORDER = Object.freeze({ page: 0, header: 1, sidebar: 2, box: 3 });
|
|
62
75
|
/** Recomputed from host descriptors; queued/off/unknown descriptors never get a frame reservation. */
|
|
63
76
|
export function allocateEffectFrames(descriptors, context) {
|
|
64
77
|
if (!Array.isArray(descriptors) || descriptors.length > 128 || !plain(context)) return [];
|
|
65
78
|
const keys = new Set(), duplicate = new Set();
|
|
66
79
|
for (const d of descriptors) if (d && typeof d.key === "string") { if (keys.has(d.key)) duplicate.add(d.key); keys.add(d.key); }
|
|
67
|
-
const result = descriptors.map(d => ({ key: d?.key, verdict: "off", reason: "
|
|
68
|
-
const gates = ["safe", "publicLink", "reducedMotion", "
|
|
69
|
-
if (gates.some(k => typeof context[k] !== "boolean") || context.openInstance !== null && !instanceName(context.openInstance)) return result;
|
|
80
|
+
const result = descriptors.map(d => ({ key: d?.key, verdict: "off", reason: "descriptor_invalid", queuePos: null }));
|
|
81
|
+
const gates = ["safe", "publicLink", "reducedMotion", "hidden", "enabled", "show", "transportReady", "constrained", "modal"];
|
|
82
|
+
if (gates.some(k => typeof context[k] !== "boolean") || context.openInstance !== null && !instanceName(context.openInstance)) return result.map(r => ({ ...r, reason: "gates_unknown" }));
|
|
70
83
|
let global = null;
|
|
71
84
|
if (context.safe === true) global = "safe_mode";
|
|
72
|
-
else if (context.publicLink === true) global = "public_link";
|
|
73
85
|
else if (context.reducedMotion === true) global = "reduced_motion";
|
|
74
|
-
else if (context.saveData === true) global = "save_data";
|
|
75
86
|
else if (context.hidden === true) global = "hidden";
|
|
76
|
-
else if (context.enabled !== true
|
|
77
|
-
else if (context.
|
|
87
|
+
else if (context.enabled !== true) global = "unsupported_host";
|
|
88
|
+
else if (context.show !== true) global = "disabled";
|
|
89
|
+
|
|
78
90
|
const candidates = [];
|
|
79
91
|
for (let i = 0; i < descriptors.length; i++) {
|
|
80
92
|
const d = descriptors[i], r = result[i];
|
|
@@ -87,6 +99,8 @@ export function allocateEffectFrames(descriptors, context) {
|
|
|
87
99
|
if (!owner && d.region !== "chat") { r.reason = "scope"; continue; }
|
|
88
100
|
if (d.region === "chat" && (!instanceName(d.instance) || d.instance !== context.openInstance)) { r.reason = "closed_chat"; continue; }
|
|
89
101
|
if (global) { r.reason = global; continue; }
|
|
102
|
+
if (context.publicLink && (d.region !== "chat" || trial)) { r.reason = "public_link"; continue; }
|
|
103
|
+
if (!context.transportReady) { r.reason = "transport_unavailable"; continue; }
|
|
90
104
|
if (d.paused === true) { r.reason = "paused"; continue; }
|
|
91
105
|
if (d.visible !== true) { r.reason = "offscreen"; continue; }
|
|
92
106
|
if (d.layer === "front" && context.modal === true && !trial) { r.reason = "modal"; continue; }
|
|
@@ -12,10 +12,14 @@ const id = v => typeof v === "string" && /^[0-9a-f]{32}$/.test(v);
|
|
|
12
12
|
/** One host-owned mount. The registry must remove the frame when remove is true, never merely hide it. */
|
|
13
13
|
export class EffectBudget {
|
|
14
14
|
constructor({ device, baselineFps, clock = () => performance.now(), longTasksSupported = false } = {}) {
|
|
15
|
-
if (!deviceClass(device) || !finite(baselineFps) || baselineFps <= 0 || baselineFps > 1000 || typeof clock !== "function" || typeof longTasksSupported !== "boolean") throw new TypeError("effect budget options");
|
|
15
|
+
if (!deviceClass(device) || baselineFps !== undefined && (!finite(baselineFps) || baselineFps <= 0 || baselineFps > 1000) || typeof clock !== "function" || typeof longTasksSupported !== "boolean") throw new TypeError("effect budget options");
|
|
16
16
|
this.device = device;
|
|
17
|
-
//
|
|
18
|
-
|
|
17
|
+
// Legacy host measurements may still be supplied by pure probes. Product playback starts
|
|
18
|
+
// immediately and learns cadence concurrently from host rAF, never Worker timing.
|
|
19
|
+
// Freeze before the first report: a mount's profile floor must remain coherent.
|
|
20
|
+
this.fpsFloor = baselineFps === undefined ? null : Math.min(EFFECT_BUDGETS[device].fps, baselineFps * 0.9);
|
|
21
|
+
this.cadence = [];
|
|
22
|
+
this.cadenceAt = null;
|
|
19
23
|
this.clock = clock;
|
|
20
24
|
this.longTasksSupported = longTasksSupported;
|
|
21
25
|
this.last = null;
|
|
@@ -58,7 +62,16 @@ export class EffectBudget {
|
|
|
58
62
|
frame() {
|
|
59
63
|
const now = this.time();
|
|
60
64
|
if (now === null) return this.stop("clock_unavailable");
|
|
61
|
-
if (this.verdict === "paused" || this.hiddenAt !== null
|
|
65
|
+
if (this.verdict === "paused" || this.hiddenAt !== null) return this.snapshot();
|
|
66
|
+
if (this.fpsFloor === null) {
|
|
67
|
+
const interval = this.cadenceAt === null ? null : now - this.cadenceAt;
|
|
68
|
+
this.cadenceAt = now;
|
|
69
|
+
if (interval !== null && interval >= 1 && interval <= 1000) {
|
|
70
|
+
if (this.cadence.length === 256) this.cadence.shift();
|
|
71
|
+
this.cadence.push(interval);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (now < this.epoch + 1000) return this.snapshot();
|
|
62
75
|
this.trim(now);
|
|
63
76
|
if (this.frames.at(-1) !== now) {
|
|
64
77
|
if (this.frames.length >= 2048) this.overflow = true;
|
|
@@ -88,10 +101,10 @@ export class EffectBudget {
|
|
|
88
101
|
const now = this.time();
|
|
89
102
|
if (now === null) return this.stop("clock_unavailable");
|
|
90
103
|
if (!plain(raw)) return this.stop("gates_unknown");
|
|
91
|
-
const { hidden, reducedMotion,
|
|
92
|
-
if ([hidden, reducedMotion,
|
|
104
|
+
const { hidden, reducedMotion, show } = raw;
|
|
105
|
+
if ([hidden, reducedMotion, show].some(v => typeof v !== "boolean")) return this.stop("gates_unknown");
|
|
93
106
|
if (this.verdict === "paused") return this.snapshot();
|
|
94
|
-
if (reducedMotion ||
|
|
107
|
+
if (reducedMotion || !show) return this.stop(reducedMotion ? "reduced_motion" : "viewer_off");
|
|
95
108
|
if (hidden && this.hiddenAt === null) this.hiddenAt = now;
|
|
96
109
|
if (hidden && now - this.hiddenAt >= 10000) return this.stop("hidden");
|
|
97
110
|
if (!hidden && this.hiddenAt !== null) {
|
|
@@ -105,6 +118,7 @@ export class EffectBudget {
|
|
|
105
118
|
this.overflow = false;
|
|
106
119
|
this.badWindows = 0;
|
|
107
120
|
this.metrics = null;
|
|
121
|
+
this.cadence = []; this.cadenceAt = null;
|
|
108
122
|
}
|
|
109
123
|
return this.snapshot();
|
|
110
124
|
}
|
|
@@ -123,8 +137,16 @@ export class EffectBudget {
|
|
|
123
137
|
const hostMs = this.longTasksSupported ? sum(null) : null;
|
|
124
138
|
const ownMs = this.longTasksSupported && this.tasks.some(t => t.attributed) ? sum(true) : null;
|
|
125
139
|
const limits = EFFECT_BUDGETS[this.device];
|
|
126
|
-
|
|
127
|
-
|
|
140
|
+
if (this.fpsFloor === null && this.cadence.length >= 3) {
|
|
141
|
+
// Faster observed intervals distinguish intermittent jank from a genuinely slower
|
|
142
|
+
// display. This is a live reference, not an empty-page health certification.
|
|
143
|
+
const sorted = [...this.cadence].sort((a, b) => a - b);
|
|
144
|
+
const interval = sorted[Math.ceil(sorted.length * (sorted.length < 8 ? 0.5 : 0.2)) - 1];
|
|
145
|
+
this.fpsFloor = Math.min(limits.fps, 900 / interval);
|
|
146
|
+
this.cadence = []; this.cadenceAt = null;
|
|
147
|
+
}
|
|
148
|
+
const bad = this.overflow || this.fpsFloor === null || fps < this.fpsFloor || hostMs !== null && hostMs > limits.longTaskMsPerSecond;
|
|
149
|
+
this.metrics = this.fpsFloor === null ? null : { at: now, hostFps: fps, hostLongTaskMsPerSecond: hostMs, attributedLongTaskMsPerSecond: ownMs,
|
|
128
150
|
attribution: ownMs === null ? "unknown" : "observed", fpsFloor: this.fpsFloor, windowMs: 2000 };
|
|
129
151
|
this.overflow = false;
|
|
130
152
|
if (bad) {
|