@songsid/agend 2.2.1 → 2.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-cli-instructions.md +5 -2
- package/dist/agent-cli.js +284 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +2 -0
- package/dist/agent-endpoint.js +29 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-file-read.d.ts +38 -0
- package/dist/agent-file-read.js +317 -0
- package/dist/agent-file-read.js.map +1 -0
- package/dist/agent-whats-new.d.ts +11 -0
- package/dist/agent-whats-new.js +34 -2
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/atomic-write.js +9 -1
- package/dist/atomic-write.js.map +1 -1
- package/dist/auth-api.d.ts +30 -1
- package/dist/auth-api.js +61 -12
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/authority-state.d.ts +38 -0
- package/dist/authority/authority-state.js +114 -0
- package/dist/authority/authority-state.js.map +1 -0
- package/dist/authority/session-authorizations.d.ts +43 -0
- package/dist/authority/session-authorizations.js +102 -0
- package/dist/authority/session-authorizations.js.map +1 -0
- package/dist/authority/web-authority.d.ts +120 -0
- package/dist/authority/web-authority.js +157 -0
- package/dist/authority/web-authority.js.map +1 -0
- package/dist/backend/antigravity.js +3 -3
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +2 -2
- package/dist/backend/claude-code.js +10 -5
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/launch-effort.js +1 -1
- package/dist/backend/launch-effort.js.map +1 -1
- package/dist/backend/muse.js +10 -2
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +14 -0
- package/dist/backend/types.js +25 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-availability.d.ts +120 -0
- package/dist/backend-availability.js +396 -0
- package/dist/backend-availability.js.map +1 -0
- package/dist/backend-command.d.ts +21 -3
- package/dist/backend-command.js +23 -4
- package/dist/backend-command.js.map +1 -1
- package/dist/bundled-skills.d.ts +14 -0
- package/dist/bundled-skills.js +29 -0
- package/dist/bundled-skills.js.map +1 -0
- package/dist/cache-analysis.d.ts +47 -1
- package/dist/cache-analysis.js +68 -8
- package/dist/cache-analysis.js.map +1 -1
- package/dist/cache-ledger.d.ts +16 -3
- package/dist/cache-ledger.js +33 -13
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-report-tool.d.ts +17 -0
- package/dist/cache-report-tool.js +40 -0
- package/dist/cache-report-tool.js.map +1 -0
- package/dist/cache-service.d.ts +70 -3
- package/dist/cache-service.js +178 -17
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord-client-retirement.d.ts +5 -0
- package/dist/channel/adapters/discord-client-retirement.js +57 -0
- package/dist/channel/adapters/discord-client-retirement.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +11 -0
- package/dist/channel/adapters/discord.js +66 -6
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +8 -1
- package/dist/channel/adapters/telegram.js +74 -4
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +48 -2
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/types.d.ts +21 -0
- package/dist/classic-channel-manager.d.ts +10 -5
- package/dist/classic-channel-manager.js +12 -9
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +142 -110
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +119 -4
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +8 -0
- package/dist/daemon.js +298 -22
- package/dist/daemon.js.map +1 -1
- package/dist/decision-list.d.ts +25 -0
- package/dist/decision-list.js +30 -0
- package/dist/decision-list.js.map +1 -0
- package/dist/delivery-outbox.d.ts +10 -0
- package/dist/delivery-outbox.js +12 -1
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/fleet-context.d.ts +15 -0
- package/dist/fleet-manager.d.ts +514 -12
- package/dist/fleet-manager.js +1885 -163
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-process-handlers.d.ts +18 -0
- package/dist/fleet-process-handlers.js +69 -0
- package/dist/fleet-process-handlers.js.map +1 -0
- package/dist/fleet-traffic.d.ts +74 -0
- package/dist/fleet-traffic.js +196 -0
- package/dist/fleet-traffic.js.map +1 -0
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +20 -5
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +3 -2
- package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +492 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +482 -0
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
- package/dist/human-delivery-receipts.d.ts +24 -1
- package/dist/human-delivery-receipts.js +64 -11
- package/dist/human-delivery-receipts.js.map +1 -1
- package/dist/instance-avatar.js +3 -0
- package/dist/instance-avatar.js.map +1 -1
- package/dist/instance-config-impact.js +6 -1
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +2 -0
- package/dist/instance-lifecycle.js +20 -10
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instance-names.d.ts +4 -0
- package/dist/instance-names.js +12 -0
- package/dist/instance-names.js.map +1 -0
- package/dist/instructions.d.ts +12 -1
- package/dist/instructions.js +13 -2
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +118 -10
- package/dist/locale.js.map +1 -1
- package/dist/login-controller.js +35 -2
- package/dist/login-controller.js.map +1 -1
- package/dist/ls-attach-resolve.d.ts +95 -0
- package/dist/ls-attach-resolve.js +174 -0
- package/dist/ls-attach-resolve.js.map +1 -0
- package/dist/memory-hogs.d.ts +27 -0
- package/dist/memory-hogs.js +124 -0
- package/dist/memory-hogs.js.map +1 -0
- package/dist/needs-you-hub.d.ts +4 -0
- package/dist/needs-you-hub.js +31 -6
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +1 -13
- package/dist/needs-you.js +3 -37
- package/dist/needs-you.js.map +1 -1
- package/dist/outbound-handlers.d.ts +15 -0
- package/dist/outbound-handlers.js +152 -11
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +228 -4
- package/dist/outbound-schemas.js +147 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +21 -1
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +9 -0
- package/dist/public-web-link.js +8 -0
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.d.ts +10 -1
- package/dist/quickstart-api.js +12 -4
- package/dist/quickstart-api.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +4 -0
- package/dist/reply-buttons-controller.js +2 -2
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/repo-worktree-guard.d.ts +36 -0
- package/dist/repo-worktree-guard.js +143 -0
- package/dist/repo-worktree-guard.js.map +1 -0
- package/dist/restart-progress.d.ts +65 -1
- package/dist/restart-progress.js +236 -10
- package/dist/restart-progress.js.map +1 -1
- package/dist/rollout-index.d.ts +5 -0
- package/dist/rollout-index.js +26 -5
- package/dist/rollout-index.js.map +1 -1
- package/dist/scheduler/scheduler.d.ts +70 -1
- package/dist/scheduler/scheduler.js +256 -17
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/service-installer.d.ts +2 -1
- package/dist/service-installer.js +18 -3
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.d.ts +23 -0
- package/dist/settings-api.js +233 -2
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +112 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +32 -2
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +5 -0
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/tmux-manager.d.ts +5 -0
- package/dist/tmux-manager.js +16 -0
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tool-permissions.d.ts +4 -6
- package/dist/tool-permissions.js +100 -2
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +17 -7
- package/dist/topic-commands.js +39 -21
- package/dist/topic-commands.js.map +1 -1
- package/dist/topic-link.d.ts +128 -0
- package/dist/topic-link.js +269 -0
- package/dist/topic-link.js.map +1 -0
- package/dist/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 +61 -1
- package/dist/ui/app-effects.js +245 -0
- package/dist/ui/chat-ambient.js +307 -0
- package/dist/ui/chat-effect-posters.js +42 -0
- package/dist/ui/chat-effects.js +160 -0
- package/dist/ui/chat-live-replies.js +58 -0
- package/dist/ui/chat-render.js +25 -7
- package/dist/ui/chat-store.js +41 -13
- package/dist/ui/chat-strings.js +86 -2
- package/dist/ui/chat-thread.js +204 -37
- package/dist/ui/chat-tour.js +1 -1
- package/dist/ui/effect-trial-controller.js +124 -0
- package/dist/ui/effect-trial-offer.js +31 -0
- package/dist/ui/effect-trial-panel.js +40 -0
- package/dist/ui/fleet-cache.js +23 -1
- package/dist/ui/fleet-canvas-layout.js +111 -0
- package/dist/ui/fleet-canvas.js +525 -0
- package/dist/ui/fleet-org.js +27 -12
- package/dist/ui/fleet-traffic.js +343 -0
- package/dist/ui/fleet-usage.js +401 -0
- package/dist/ui/image-lightbox.js +1 -1
- package/dist/ui/panel-chat.js +252 -58
- package/dist/ui/panel-details.js +20 -6
- package/dist/ui/panel-fleet.js +45 -19
- package/dist/ui/panel-log.js +6 -6
- package/dist/ui/panel-needs.js +3 -3
- package/dist/ui/panel-settings.js +58 -13
- package/dist/ui/preview-panel.js +25 -9
- package/dist/ui/preview.js +3 -2
- package/dist/ui/settings-appearance.js +349 -0
- package/dist/ui/settings-catalog.js +92 -0
- package/dist/ui/settings-dialogs.js +40 -12
- package/dist/ui/settings-effects.js +202 -0
- package/dist/ui/settings-guests.js +117 -0
- package/dist/ui/settings-learn.js +25 -0
- package/dist/ui/settings-model.js +15 -6
- package/dist/ui/settings-mods.js +271 -0
- package/dist/ui/settings-owner-effects.js +80 -0
- package/dist/ui/settings-strings.js +335 -10
- package/dist/ui/settings-web-authority.js +91 -0
- package/dist/ui/shared/agend-auth.js +28 -4
- package/dist/ui/shared/agent-widget.js +248 -0
- package/dist/ui/shared/app-i18n.js +6 -2
- package/dist/ui/shared/app-prefs.js +34 -13
- package/dist/ui/shared/app-route.js +42 -2
- package/dist/ui/shared/app-session.js +3 -1
- package/dist/ui/shared/app-shell.js +119 -41
- package/dist/ui/shared/app-store.js +28 -1
- package/dist/ui/shared/app-stream.js +14 -7
- package/dist/ui/shared/app-themes.js +206 -0
- package/dist/ui/shared/app.css +566 -5
- package/dist/ui/shared/app.js +23 -10
- package/dist/ui/shared/card-strings.js +32 -0
- package/dist/ui/shared/effect-app-runtime.js +297 -0
- package/dist/ui/shared/effect-baseline.js +64 -0
- package/dist/ui/shared/effect-chat-runtime.js +167 -0
- package/dist/ui/shared/effect-cursor.js +125 -0
- package/dist/ui/shared/effect-frame.js +146 -0
- package/dist/ui/shared/effect-geometry.js +137 -0
- package/dist/ui/shared/effect-host-style.js +10 -0
- package/dist/ui/shared/effect-owner-regions.js +74 -0
- package/dist/ui/shared/effect-owner-source.js +122 -0
- package/dist/ui/shared/effect-policy.js +151 -0
- package/dist/ui/shared/effect-poster-reader.js +98 -0
- package/dist/ui/shared/effect-posters.js +117 -0
- package/dist/ui/shared/effect-profiler.js +184 -0
- package/dist/ui/shared/effect-profiles.js +65 -0
- package/dist/ui/shared/effect-registry.js +543 -0
- package/dist/ui/shared/effect-runtime-loader.js +234 -0
- package/dist/ui/shared/effect-safety.js +153 -0
- package/dist/ui/shared/effect-stock-snapshot.js +43 -0
- package/dist/ui/shared/effect-stock-trust.js +217 -0
- package/dist/ui/shared/effect-trial-shadow.js +62 -0
- package/dist/ui/shared/effect-trial-source.js +103 -0
- package/dist/ui/shared/effect-trial-stage.js +170 -0
- package/dist/ui/shared/effect-trial.css +40 -0
- package/dist/ui/shared/fx-motion.js +67 -0
- package/dist/ui/shared/header-tools.js +1 -1
- package/dist/ui/shared/identity-card.js +273 -0
- package/dist/ui/shared/identity-color.js +261 -0
- package/dist/ui/shared/instance-nav.js +215 -41
- package/dist/ui/shared/owner-css.js +250 -0
- package/dist/ui/shared/owner-effect-prefs.js +117 -0
- package/dist/ui/shared/panel-view.js +103 -10
- package/dist/ui/shared/signin.js +2 -0
- package/dist/ui/shared/tokens.css +9 -3
- package/dist/ui/shared/ui-dialog.js +1 -1
- package/dist/ui/shared/ui-icons.js +3 -0
- package/dist/ui/shared/ui-menu.js +1 -1
- package/dist/ui/shared/ui-toast.js +1 -1
- package/dist/ui/shared/usage-dialog.js +22 -2
- package/dist/ui/shared/view-strings.js +98 -2
- package/dist/ui/shared/web-authorize.js +151 -0
- package/dist/ui/signin.html +1 -0
- package/dist/ui/web-terminal/terminal.css +4 -4
- package/dist/usage/providers.d.ts +28 -0
- package/dist/usage/providers.js +126 -36
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage-report.d.ts +158 -0
- package/dist/usage-report.js +226 -0
- package/dist/usage-report.js.map +1 -0
- package/dist/usage-rollup.d.ts +51 -0
- package/dist/usage-rollup.js +168 -0
- package/dist/usage-rollup.js.map +1 -0
- package/dist/view-api.d.ts +68 -0
- package/dist/view-api.js +249 -8
- package/dist/view-api.js.map +1 -1
- package/dist/web-ambient.d.ts +72 -0
- package/dist/web-ambient.js +161 -0
- package/dist/web-ambient.js.map +1 -0
- package/dist/web-api.d.ts +117 -7
- package/dist/web-api.js +1283 -70
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-limits.d.ts +82 -0
- package/dist/web-appearance-limits.js +83 -0
- package/dist/web-appearance-limits.js.map +1 -0
- package/dist/web-appearance-policy.d.ts +64 -0
- package/dist/web-appearance-policy.js +87 -0
- package/dist/web-appearance-policy.js.map +1 -0
- package/dist/web-appearance-tools.d.ts +53 -0
- package/dist/web-appearance-tools.js +124 -0
- package/dist/web-appearance-tools.js.map +1 -0
- package/dist/web-appearance-view.d.ts +124 -0
- package/dist/web-appearance-view.js +278 -0
- package/dist/web-appearance-view.js.map +1 -0
- package/dist/web-appearance.d.ts +93 -0
- package/dist/web-appearance.js +373 -0
- package/dist/web-appearance.js.map +1 -0
- package/dist/web-asset-parts.d.ts +93 -0
- package/dist/web-asset-parts.js +133 -0
- package/dist/web-asset-parts.js.map +1 -0
- package/dist/web-auth.js +20 -2
- package/dist/web-auth.js.map +1 -1
- package/dist/web-card.d.ts +110 -0
- package/dist/web-card.js +266 -0
- package/dist/web-card.js.map +1 -0
- package/dist/web-channel-echo.d.ts +7 -1
- package/dist/web-channel-echo.js +6 -2
- package/dist/web-channel-echo.js.map +1 -1
- package/dist/web-chat-history.d.ts +63 -0
- package/dist/web-chat-history.js +109 -6
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +1 -0
- package/dist/web-effect-bootstrap.d.ts +12 -0
- package/dist/web-effect-bootstrap.js +49 -0
- package/dist/web-effect-bootstrap.js.map +1 -0
- package/dist/web-effect-bundle.d.ts +28 -0
- package/dist/web-effect-bundle.js +171 -0
- package/dist/web-effect-bundle.js.map +1 -0
- package/dist/web-effect-canonical.d.ts +8 -0
- package/dist/web-effect-canonical.js +27 -0
- package/dist/web-effect-canonical.js.map +1 -0
- package/dist/web-effect-library.d.ts +114 -0
- package/dist/web-effect-library.js +530 -0
- package/dist/web-effect-library.js.map +1 -0
- package/dist/web-effect-playback.d.ts +30 -0
- package/dist/web-effect-playback.js +71 -0
- package/dist/web-effect-playback.js.map +1 -0
- package/dist/web-effect-poster.d.ts +8 -0
- package/dist/web-effect-poster.js +52 -0
- package/dist/web-effect-poster.js.map +1 -0
- package/dist/web-effect-profiles.d.ts +118 -0
- package/dist/web-effect-profiles.js +180 -0
- package/dist/web-effect-profiles.js.map +1 -0
- package/dist/web-effect-service.d.ts +93 -0
- package/dist/web-effect-service.js +335 -0
- package/dist/web-effect-service.js.map +1 -0
- package/dist/web-effect-tools.d.ts +48 -0
- package/dist/web-effect-tools.js +129 -0
- package/dist/web-effect-tools.js.map +1 -0
- package/dist/web-effect-trials.d.ts +86 -0
- package/dist/web-effect-trials.js +204 -0
- package/dist/web-effect-trials.js.map +1 -0
- package/dist/web-effect-upload.d.ts +27 -0
- package/dist/web-effect-upload.js +185 -0
- package/dist/web-effect-upload.js.map +1 -0
- package/dist/web-effect-worker.d.ts +8 -0
- package/dist/web-effect-worker.js +170 -0
- package/dist/web-effect-worker.js.map +1 -0
- package/dist/web-effect.d.ts +103 -0
- package/dist/web-effect.js +153 -0
- package/dist/web-effect.js.map +1 -0
- package/dist/web-guest-access.d.ts +125 -0
- package/dist/web-guest-access.js +234 -0
- package/dist/web-guest-access.js.map +1 -0
- package/dist/web-guest-rooms.d.ts +82 -0
- package/dist/web-guest-rooms.js +135 -0
- package/dist/web-guest-rooms.js.map +1 -0
- package/dist/web-identity-color.d.ts +87 -0
- package/dist/web-identity-color.js +209 -0
- package/dist/web-identity-color.js.map +1 -0
- package/dist/web-identity-palette.d.ts +113 -0
- package/dist/web-identity-palette.js +84 -0
- package/dist/web-identity-palette.js.map +1 -0
- package/dist/web-image-meta.d.ts +38 -0
- package/dist/web-image-meta.js +262 -0
- package/dist/web-image-meta.js.map +1 -0
- package/dist/web-login.d.ts +47 -0
- package/dist/web-login.js +115 -0
- package/dist/web-login.js.map +1 -1
- package/dist/web-mod-agent.d.ts +50 -0
- package/dist/web-mod-agent.js +207 -0
- package/dist/web-mod-agent.js.map +1 -0
- package/dist/web-mod-bundle.d.ts +157 -0
- package/dist/web-mod-bundle.js +529 -0
- package/dist/web-mod-bundle.js.map +1 -0
- package/dist/web-mod-export.d.ts +68 -0
- package/dist/web-mod-export.js +100 -0
- package/dist/web-mod-export.js.map +1 -0
- package/dist/web-mod-store.d.ts +289 -0
- package/dist/web-mod-store.js +638 -0
- package/dist/web-mod-store.js.map +1 -0
- package/dist/web-org.d.ts +3 -3
- package/dist/web-org.js +11 -17
- package/dist/web-org.js.map +1 -1
- package/dist/web-owner-css-store.d.ts +222 -0
- package/dist/web-owner-css-store.js +440 -0
- package/dist/web-owner-css-store.js.map +1 -0
- package/dist/web-owner-css.d.ts +58 -0
- package/dist/web-owner-css.js +552 -0
- package/dist/web-owner-css.js.map +1 -0
- package/dist/web-prefs.d.ts +62 -0
- package/dist/web-prefs.js +232 -36
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +15 -3
- package/dist/web-preview.js +28 -9
- package/dist/web-preview.js.map +1 -1
- package/dist/web-returning-device.d.ts +41 -0
- package/dist/web-returning-device.js +57 -5
- package/dist/web-returning-device.js.map +1 -1
- package/dist/web-session-authorization.d.ts +132 -0
- package/dist/web-session-authorization.js +193 -0
- package/dist/web-session-authorization.js.map +1 -0
- package/dist/web-session.d.ts +70 -10
- package/dist/web-session.js +137 -22
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +67 -2
- package/dist/web-settings-patch.js +196 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +89 -0
- package/dist/web-settings-tools.js +273 -0
- package/dist/web-settings-tools.js.map +1 -0
- package/dist/web-shell-routes.d.ts +2 -2
- package/dist/web-shell-routes.js +2 -2
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/web-terminal-http.js +15 -2
- package/dist/web-terminal-http.js.map +1 -1
- package/dist/web-theme-store.d.ts +257 -0
- package/dist/web-theme-store.js +627 -0
- package/dist/web-theme-store.js.map +1 -0
- package/dist/web-theme.d.ts +123 -0
- package/dist/web-theme.js +416 -0
- package/dist/web-theme.js.map +1 -0
- package/dist/web-upload.d.ts +13 -0
- package/dist/web-upload.js +39 -3
- package/dist/web-upload.js.map +1 -1
- package/dist/web-widget.d.ts +72 -0
- package/dist/web-widget.js +245 -0
- package/dist/web-widget.js.map +1 -0
- package/dist/workflow-templates/default.md +1 -1
- package/package.json +1 -1
- package/templates/systemd.service.ejs +8 -0
package/dist/ui/shared/app.js
CHANGED
|
@@ -12,12 +12,14 @@ import { Shell, handleKey, showDialog, closeDrawer, openDrawer, shellStore, retr
|
|
|
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";
|
|
19
19
|
import { t } from "./app-i18n.js";
|
|
20
20
|
import { initTextSize, initUsageRail } from "./header-tools.js";
|
|
21
|
+
import { installOwnerCss } from "./owner-css.js";
|
|
22
|
+
import { startThemes } from "./app-themes.js";
|
|
21
23
|
|
|
22
24
|
const boot = document.body.dataset;
|
|
23
25
|
// #1523 N3: this device's text size, on the root before the first paint of any panel.
|
|
@@ -27,6 +29,9 @@ initUsageRail();
|
|
|
27
29
|
// session-only panel, nothing under /ui/ is ever imported or read (#1408 §3).
|
|
28
30
|
const mode = boot.mode === "view-only" ? "view-only" : "full";
|
|
29
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();
|
|
30
35
|
|
|
31
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
|
|
32
37
|
// module import cached under the first one. boot() runs once, whichever attempt succeeds.
|
|
@@ -78,17 +83,22 @@ if (mode === "full") {
|
|
|
78
83
|
// a boot import that failed before the Outlet first asked would be retried at once, depending on timing.)
|
|
79
84
|
let chatLoads = 0;
|
|
80
85
|
panels.set("chat", { load: () => (chatLoads++ === 0 ? chatBoot : loadChat()).then((m) => m.ChatPanel) });
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
+
}
|
|
90
97
|
// #1665: this device's first upload of its own choices (once), then another device's changes at each status frame.
|
|
98
|
+
// A guest's page only reads them (prefsWritable is false there).
|
|
91
99
|
bootPrefs();
|
|
100
|
+
// #1629: agents' themes, read when the status frame's themes_rev moves (a guest's frames never carry it).
|
|
101
|
+
if (!guest) startThemes();
|
|
92
102
|
onNewInstance = async () => {
|
|
93
103
|
closeDrawer(); // on a phone the dialog opens from the drawer: the drawer goes first
|
|
94
104
|
let m;
|
|
@@ -99,6 +109,9 @@ if (mode === "full") {
|
|
|
99
109
|
} else appStore.set({ connection: "none" });
|
|
100
110
|
|
|
101
111
|
startRouter(window);
|
|
112
|
+
// #1629 r4 §3: the person's owner CSS (full app only; never on Settings, in safe mode or where this device turned it off;
|
|
113
|
+
// never a guest's — owner CSS is an admin's).
|
|
114
|
+
if (!guest) installOwnerCss();
|
|
102
115
|
// /ui on its own opens the chat this browser had open last (an unknown name shows the panel's "not found").
|
|
103
116
|
{
|
|
104
117
|
const r = navStore.get().route;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// #1629 r4 §1 / r4.1: the identity card's words (card.*), in English and zh-TW. Public (/assets/): View's anonymous
|
|
2
|
+
// reader sees a card too.
|
|
3
|
+
import { register } from "./app-i18n.js";
|
|
4
|
+
|
|
5
|
+
register("card", {
|
|
6
|
+
en: {
|
|
7
|
+
label: "Identity card of {0}",
|
|
8
|
+
portraitAlt: "{0}'s portrait",
|
|
9
|
+
size: "Card size",
|
|
10
|
+
sizeS: "S", sizeM: "M", sizeL: "L",
|
|
11
|
+
sizeSName: "Small", sizeMName: "Medium", sizeLName: "Large",
|
|
12
|
+
byHuman: "set by a person",
|
|
13
|
+
byAgent: "set by the agent",
|
|
14
|
+
show: "Show card",
|
|
15
|
+
hide: "Hide card",
|
|
16
|
+
bannerSafe: "Every screen",
|
|
17
|
+
bannerPhone: "Phone · M card",
|
|
18
|
+
},
|
|
19
|
+
"zh-TW": {
|
|
20
|
+
label: "{0} 的身分卡",
|
|
21
|
+
portraitAlt: "{0} 的肖像",
|
|
22
|
+
size: "卡片大小",
|
|
23
|
+
sizeS: "小", sizeM: "中", sizeL: "大",
|
|
24
|
+
sizeSName: "小", sizeMName: "中", sizeLName: "大",
|
|
25
|
+
byHuman: "由人設定",
|
|
26
|
+
byAgent: "由 agent 設定",
|
|
27
|
+
show: "顯示卡片",
|
|
28
|
+
hide: "隱藏卡片",
|
|
29
|
+
bannerSafe: "所有螢幕",
|
|
30
|
+
bannerPhone: "手機 · 中卡片",
|
|
31
|
+
},
|
|
32
|
+
});
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/** #1629: app-owned pool; Chat contributes a lease, never another registry/loader. */
|
|
2
|
+
import { effectPlaybackReason, EFFECT_BLOCK_REASONS } from "./effect-policy.js";
|
|
3
|
+
import { createEffectTrialSource } from "./effect-trial-source.js";
|
|
4
|
+
import { createOwnerEffectSource } from "./effect-owner-source.js";
|
|
5
|
+
import { createEffectRuntimeLoader } from "./effect-runtime-loader.js";
|
|
6
|
+
import { createEffectRegistry } from "./effect-registry.js";
|
|
7
|
+
import { createEffectSafety } from "./effect-safety.js";
|
|
8
|
+
import { stockEffectTrust } from "./effect-stock-trust.js";
|
|
9
|
+
import { createEffectProfileCollector } from "./effect-profiles.js";
|
|
10
|
+
import { effectPlaybackIdentity, createEffectWriter } from "./effect-chat-runtime.js";
|
|
11
|
+
|
|
12
|
+
const episodes = new WeakSet();
|
|
13
|
+
|
|
14
|
+
/** `current` belongs to the page/session episode. Route cleanup only releases its contribution. */
|
|
15
|
+
export function createAppEffectRuntime({ window: host, current, observe, onChange = () => {}, onDispose = () => {}, resolveOwnerTarget = null, onOwnerUnavailable = () => {}, factories = {} }) {
|
|
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;
|
|
17
|
+
const bindings = new Map(), profiles = new Map();
|
|
18
|
+
let lastContext = null, playbackUnavailable = null;
|
|
19
|
+
const identity = effectPlaybackIdentity(host), auth = host?.AgendAuth;
|
|
20
|
+
const safe = fn => { try { return fn(); } catch { return null; } };
|
|
21
|
+
const alive = () => { if (dead) return false; const value = safe(() => current() === true && !auth.sessionEnded()); return !dead && value === true; };
|
|
22
|
+
const sourceAlive = s => {
|
|
23
|
+
if (!s || !alive() || s !== source || s.dead) return false;
|
|
24
|
+
const value = safe(() => s.current() === true && s.element.isConnected === true);
|
|
25
|
+
return !dead && s === source && !s.dead && value === true;
|
|
26
|
+
};
|
|
27
|
+
const placementIdentity = p => JSON.stringify([p.by, p.manifest.layer, p.params]);
|
|
28
|
+
const clock = () => host.performance.now();
|
|
29
|
+
let offAuth = null;
|
|
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
|
+
};
|
|
40
|
+
function tell() {
|
|
41
|
+
const s = source, value = snapshot(s); if (dead) return;
|
|
42
|
+
if (sourceAlive(s)) {
|
|
43
|
+
const next = JSON.stringify(value);
|
|
44
|
+
if (next !== s.lastSnapshot) { s.lastSnapshot = next; safe(() => s.onChange(value)); }
|
|
45
|
+
}
|
|
46
|
+
if (!dead) safe(() => onChange(snapshot(source)));
|
|
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
|
+
}
|
|
69
|
+
function discard(s) {
|
|
70
|
+
if (!s || s.dead) return;
|
|
71
|
+
s.dead = true; s.generation++; s.metadata = null;
|
|
72
|
+
safe(() => host.clearTimeout(s.reportTimer)); safe(() => s.reporter?.dispose());
|
|
73
|
+
if (source === s) source = null;
|
|
74
|
+
safe(() => trial?.stopFor(s));
|
|
75
|
+
for (const b of s.bindings.values()) bindings.delete(b.key); s.bindings.clear();
|
|
76
|
+
if (pending?.source === s) safe(() => pending.controller.abort());
|
|
77
|
+
revision++; stamp = null;
|
|
78
|
+
}
|
|
79
|
+
function dispose() {
|
|
80
|
+
if (dead) return; dead = true; discard(source);
|
|
81
|
+
safe(() => trial?.dispose()); safe(() => pending?.controller.abort()); safe(() => registry?.dispose?.()); safe(() => loader?.dispose?.()); safe(() => personal?.dispose()); safe(() => writer?.dispose()); safe(() => offAuth?.());
|
|
82
|
+
safe(() => host?.removeEventListener?.("pagehide", dispose));
|
|
83
|
+
for (const row of profiles.values()) safe(() => row.collector.dispose()); profiles.clear(); bindings.clear(); safe(() => onDispose({ error }));
|
|
84
|
+
}
|
|
85
|
+
function fail(reason) { if (dead) return; error = reason; dispose(); safe(() => onChange({ error, rows: [], effects: [], loading: false })); }
|
|
86
|
+
const unavailable = { ok: false, sync() {}, changedChat() {}, reply() {}, detachChat() {}, attachChat() { return null; }, attachTrial() { return null; }, stopTrial() {}, trialSnapshot: () => ({ rows: [] }), snapshot: () => snapshot(null), dispose };
|
|
87
|
+
if (!host || typeof host !== "object" || episodes.has(host) || !identity || typeof current !== "function" || typeof observe !== "function" || typeof auth?.onSessionEnd !== "function" || typeof auth?.sessionEnded !== "function") return unavailable;
|
|
88
|
+
episodes.add(host); // Claim before invoking current/auth callbacks: one attempt per Window episode.
|
|
89
|
+
if (!alive()) { dispose(); return unavailable; }
|
|
90
|
+
try {
|
|
91
|
+
offAuth = auth.onSessionEnd(dispose);
|
|
92
|
+
if (!alive()) { dispose(); safe(() => offAuth?.()); return unavailable; }
|
|
93
|
+
host.addEventListener?.("pagehide", dispose);
|
|
94
|
+
} catch { fail("runtime_unavailable"); return unavailable; }
|
|
95
|
+
const make = (key, fn) => factories[key] ?? fn;
|
|
96
|
+
function bindingAlive(b) { if (b?.kind === "trial") return trial?.current(b) === true; if (b?.kind === "owner") return personal?.current(b) === true; return !!b && sourceAlive(b.source) && b.source.generation === b.generation && bindings.get(b.key) === b && b.source.bindings.get(b.id) === b; }
|
|
97
|
+
const bindingFor = (key, id) => { const b = bindings.get(key) ?? trial?.binding(key, id) ?? personal?.binding(key, id); return b?.id === id && bindingAlive(b) ? b : null; };
|
|
98
|
+
try {
|
|
99
|
+
writer = createEffectWriter(host, alive);
|
|
100
|
+
loader = make("loader", createEffectRuntimeLoader)({ window: host, ownerEpoch: identity.epoch, current: alive, onRevoke: () => fail("runtime_unavailable") });
|
|
101
|
+
if (!loader.ok || typeof loader.placementsSettled !== "function" || typeof loader.resourceSettled !== "function") throw Error("runtime_unavailable");
|
|
102
|
+
registry = make("registry", createEffectRegistry)({ window: host, origin: host.location?.origin, boot: host.document.body.dataset.effectBoot,
|
|
103
|
+
load: async (descriptor, signal) => {
|
|
104
|
+
const b = bindingFor(descriptor.key, descriptor.id);
|
|
105
|
+
if (!bindingAlive(b) || descriptor.id !== b.id || signal.aborted) return null;
|
|
106
|
+
if (b.kind === "trial") return trial.load(b, signal);
|
|
107
|
+
if (b.kind === "owner") return personal.load(b, signal);
|
|
108
|
+
const value = await loader.resourceSettled({ id: b.id, instance: b.source.name }, signal);
|
|
109
|
+
if (!bindingAlive(b) || signal.aborted) return null;
|
|
110
|
+
if (!value) { refuseSource(b.source); return null; }
|
|
111
|
+
return value;
|
|
112
|
+
},
|
|
113
|
+
safety: make("safety", createEffectSafety)({ storage: host.localStorage, crypto: host.crypto }),
|
|
114
|
+
stockSnapshot: ({ frameRoot }) => make("stock", stockEffectTrust)(host, { frameRoot, ownerCss: observe().ownerCss }),
|
|
115
|
+
onChange() { try { tell(); emit(); } catch { fail("runtime_unavailable"); } },
|
|
116
|
+
onMount(owner) {
|
|
117
|
+
const b = bindingFor(owner.key, owner.id); if (identity.scope !== "owner" || !bindingAlive(b) || b.id !== owner.id || !owner.current()) return;
|
|
118
|
+
const collector = make("collector", createEffectProfileCollector)({ key: owner.key, id: owner.id, device: owner.device, clock });
|
|
119
|
+
const began = safe(clock);
|
|
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?.();
|
|
121
|
+
},
|
|
122
|
+
onRetire(owner) { profiles.get(owner)?.collector.dispose(); profiles.delete(owner); },
|
|
123
|
+
onProfile(raw, owner) {
|
|
124
|
+
const row = profiles.get(owner); if (!row || !bindingAlive(row.binding) || !owner.current()) return;
|
|
125
|
+
row.collector.add(raw); if (row.binding.kind === "trial") return; // Diagnostic samples stay transient; shared safety still runs in registry.
|
|
126
|
+
const now = clock();
|
|
127
|
+
if (!Number.isFinite(now) || now < row.began || now - row.began < 10000 && raw.verdict !== "paused" || row.attempted !== null && now - row.attempted < 10000) return;
|
|
128
|
+
const report = row.collector.report(); if (!report || !bindingAlive(row.binding) || !owner.current()) return;
|
|
129
|
+
row.attempted = now;
|
|
130
|
+
const query = row.binding.kind === "owner" ? "" : `instance=${encodeURIComponent(row.binding.source.name)}&`;
|
|
131
|
+
void writer.post(`/ui/effects/profile/${owner.id}?${query}ownerEpoch=${identity.epoch}`, report.profile);
|
|
132
|
+
}, clock,
|
|
133
|
+
});
|
|
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: () => {
|
|
142
|
+
if (!alive()) return false; const v = observe(), c = v.context;
|
|
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;
|
|
145
|
+
}, ownerCurrent: (s, name) => sourceAlive(s) && s.name === name,
|
|
146
|
+
stock: () => registry.frameRoot ? make("stock", stockEffectTrust)(host, { frameRoot: registry.frameRoot, ownerCss: observe().ownerCss }) : null,
|
|
147
|
+
readResource: (spec, signal) => typeof loader.trialResourceSettled === "function" ? loader.trialResourceSettled(spec, signal) : Promise.resolve(null),
|
|
148
|
+
onChange() { revision++; stamp = null; sync(); tell(); },
|
|
149
|
+
});
|
|
150
|
+
if (identity.scope === "owner" && typeof resolveOwnerTarget === "function") {
|
|
151
|
+
if (typeof loader.ownerSettled !== "function") throw Error("runtime_unavailable");
|
|
152
|
+
personal = createOwnerEffectSource({ current: alive,
|
|
153
|
+
readMetadata: signal => loader.ownerSettled(signal), readResource: (spec, signal) => loader.resourceSettled(spec, signal), resolveTarget: resolveOwnerTarget,
|
|
154
|
+
stock: () => registry.frameRoot ? make("stock", stockEffectTrust)(host, { frameRoot: registry.frameRoot, ownerCss: observe().ownerCss }) : null,
|
|
155
|
+
onChange() { revision++; stamp = null; sync(); tell(); }, onUnavailable: (slot, id) => safe(() => onOwnerUnavailable(slot, id)),
|
|
156
|
+
});
|
|
157
|
+
if (!personal.ok) throw Error("runtime_unavailable");
|
|
158
|
+
}
|
|
159
|
+
} catch { fail("runtime_unavailable"); return unavailable; }
|
|
160
|
+
function descriptors(s) {
|
|
161
|
+
if (!sourceAlive(s) || s.error) return [];
|
|
162
|
+
return (s.metadata?.effects ?? []).filter(p => p.manifest.regions.includes("chat")).map((p, order) => {
|
|
163
|
+
let b = s.bindings.get(p.id); const identity = placementIdentity(p);
|
|
164
|
+
if (!b || b.identity !== identity) {
|
|
165
|
+
if (b) bindings.delete(b.key);
|
|
166
|
+
b = Object.freeze({ key: `chat:${s.serial}:${p.id}`, id: p.id, source: s, generation: s.generation, identity });
|
|
167
|
+
s.bindings.set(p.id, b); bindings.set(b.key, b);
|
|
168
|
+
}
|
|
169
|
+
return { key: b.key, id: p.id, by: p.by, region: "chat", layer: p.manifest.layer, order, paused: s.paused.get(p.id) === identity, visible: true, instance: s.name, params: p.params, element: s.element };
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function observation() {
|
|
173
|
+
const value = observe();
|
|
174
|
+
return { ...value, context: { ...value.context, openInstance: sourceAlive(source) ? source.name : null } };
|
|
175
|
+
}
|
|
176
|
+
function emit() {
|
|
177
|
+
if (!alive() || !registry?.ok) return;
|
|
178
|
+
const s = source, value = observation();
|
|
179
|
+
if (sourceAlive(s)) { const state = safe(() => s.observe().state); if (sourceAlive(s) && ["idle", "working", "needs_you", "stopped"].includes(state)) registry.publish("state", { state }, s.name); }
|
|
180
|
+
if (alive() && ["light", "dark"].includes(value.mode)) registry.publish("theme", { mode: value.mode });
|
|
181
|
+
}
|
|
182
|
+
function refuseSource(s) {
|
|
183
|
+
if (!sourceAlive(s)) return;
|
|
184
|
+
s.error = "resource_unavailable"; s.generation++;
|
|
185
|
+
for (const b of s.bindings.values()) bindings.delete(b.key); s.bindings.clear(); revision++; stamp = null; sync(); tell();
|
|
186
|
+
}
|
|
187
|
+
function queueSync() {
|
|
188
|
+
if (queued || dead) return; queued = true;
|
|
189
|
+
Promise.resolve().then(() => { queued = false; if (alive()) sync(); else dispose(); });
|
|
190
|
+
}
|
|
191
|
+
function sync() {
|
|
192
|
+
if (!alive()) { dispose(); return; } if (syncing) { queueSync(); return; }
|
|
193
|
+
syncing = true;
|
|
194
|
+
try {
|
|
195
|
+
const candidate = source;
|
|
196
|
+
if (candidate && !sourceAlive(candidate)) discard(candidate);
|
|
197
|
+
const before = revision, value = observation(), s = source;
|
|
198
|
+
if (!alive() || revision !== before || s !== source) { if (alive()) queueSync(); return; }
|
|
199
|
+
const entries = [...(personal?.descriptors() ?? []), ...(trial?.descriptors() ?? []), ...descriptors(s)];
|
|
200
|
+
if (!alive() || revision !== before || s !== source) { stamp = null; if (alive()) queueSync(); return; }
|
|
201
|
+
lastContext = { ...value.context };
|
|
202
|
+
const next = JSON.stringify([revision, value.context, entries.map(d => [d.key, d.paused])]);
|
|
203
|
+
if (stamp !== next) {
|
|
204
|
+
if (!alive() || revision !== before || s !== source) { stamp = null; if (alive()) queueSync(); return; }
|
|
205
|
+
stamp = next; registry.update(entries, value.context);
|
|
206
|
+
}
|
|
207
|
+
if (!alive() || revision !== before || s !== source) { stamp = null; if (alive()) queueSync(); return; }
|
|
208
|
+
emit();
|
|
209
|
+
const c = value.context;
|
|
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;
|
|
214
|
+
if (!alive() || revision !== before || s !== source) { if (alive()) queueSync(); return; }
|
|
215
|
+
if (ownerNeeds && (!chatNeeds || lastMetadata !== "owner")) void readPersonal();
|
|
216
|
+
else if (chatNeeds) void read(s);
|
|
217
|
+
}
|
|
218
|
+
tell();
|
|
219
|
+
} catch { fail("runtime_unavailable"); }
|
|
220
|
+
finally { syncing = false; }
|
|
221
|
+
}
|
|
222
|
+
async function readPersonal() {
|
|
223
|
+
if (!personal || pending || !personal.needsMetadata() || !alive()) return;
|
|
224
|
+
const op = { source: personal, controller: new host.AbortController() }; pending = op; lastMetadata = "owner";
|
|
225
|
+
try { await personal.read(op.controller.signal); }
|
|
226
|
+
catch { fail("runtime_unavailable"); }
|
|
227
|
+
finally { if (pending === op) { pending = null; if (alive()) sync(); } }
|
|
228
|
+
}
|
|
229
|
+
function changedOwner() {
|
|
230
|
+
if (!personal || !alive()) return;
|
|
231
|
+
if (pending?.source === personal) safe(() => pending.controller.abort());
|
|
232
|
+
personal.invalidate();
|
|
233
|
+
}
|
|
234
|
+
function ownerSnapshot() {
|
|
235
|
+
return { ...(personal?.snapshot() ?? { error: null, rev: -1, loading: false, slots: {} }),
|
|
236
|
+
rows: registry?.snapshot().flatMap(row => { const b = personal?.binding(row.key, row.id); return b ? [{ ...row, slot: b.slot }] : []; }) ?? [] };
|
|
237
|
+
}
|
|
238
|
+
async function cursorImageSettled(selection, signal) {
|
|
239
|
+
const own = personal?.cursorSelection();
|
|
240
|
+
if (!alive() || !own || !personal.cursorCurrent(selection) || signal?.aborted) return null;
|
|
241
|
+
let value; try { value = await loader.cursorImageSettled(own.id, signal); } catch { value = null; }
|
|
242
|
+
if (!alive() || !personal.cursorCurrent(own) || !personal.cursorCurrent(selection) || signal?.aborted) return null;
|
|
243
|
+
if (!value) { personal.refuseCursor(own); return null; }
|
|
244
|
+
return value;
|
|
245
|
+
}
|
|
246
|
+
async function read(s) {
|
|
247
|
+
if (!sourceAlive(s) || pending || s.error) return;
|
|
248
|
+
const op = { source: s, generation: s.generation, controller: new host.AbortController() }; pending = op; lastMetadata = "chat";
|
|
249
|
+
try {
|
|
250
|
+
const answer = await loader.placementsSettled(s.name, op.controller.signal);
|
|
251
|
+
if (!sourceAlive(s) || op.generation !== s.generation || pending !== op || op.controller.signal.aborted) return;
|
|
252
|
+
if (!answer) { refuseSource(s); return; }
|
|
253
|
+
s.metadata = answer;
|
|
254
|
+
for (const [id, identity] of s.paused) if (!answer.effects.some(p => p.id === id && placementIdentity(p) === identity)) s.paused.delete(id);
|
|
255
|
+
revision++; stamp = null; sync(); tell();
|
|
256
|
+
} catch { if (sourceAlive(s) && op.generation === s.generation) refuseSource(s); }
|
|
257
|
+
finally { if (pending === op) { pending = null; if (alive()) sync(); } }
|
|
258
|
+
}
|
|
259
|
+
function changedSource(s) {
|
|
260
|
+
if (!sourceAlive(s) || s.error) return;
|
|
261
|
+
s.generation++; s.metadata = null;
|
|
262
|
+
for (const b of s.bindings.values()) bindings.delete(b.key); s.bindings.clear();
|
|
263
|
+
if (pending?.source === s) safe(() => pending.controller.abort()); revision++; stamp = null; sync(); tell();
|
|
264
|
+
}
|
|
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); }
|
|
266
|
+
function attachChat(options) {
|
|
267
|
+
discard(source); const expected = revision; sync();
|
|
268
|
+
if (!alive() || revision !== expected || source !== null) return null;
|
|
269
|
+
const { name, element, current: ownCurrent, observe: ownObserve, onChange: ownChange = () => {} } = options ?? {};
|
|
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;
|
|
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() };
|
|
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;
|
|
276
|
+
const placement = id => sourceAlive(s) && !s.error ? s.metadata?.effects.find(p => p.id === id) : null;
|
|
277
|
+
const changed = () => changedSource(s);
|
|
278
|
+
const api = { ok: true, sync,
|
|
279
|
+
changed,
|
|
280
|
+
reply(event) { publishReply(s, event); },
|
|
281
|
+
pause(id) { const p = placement(id); if (!p) return false; s.paused.set(id, placementIdentity(p)); revision++; sync(); return true; },
|
|
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); },
|
|
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; },
|
|
284
|
+
snapshot: () => sourceAlive(s) ? snapshot(s) : { error: null, rows: [], effects: [], loading: false },
|
|
285
|
+
dispose() { if (source !== s) return; discard(s); sync(); },
|
|
286
|
+
};
|
|
287
|
+
sync(); tell(); return sourceAlive(s) ? api : null;
|
|
288
|
+
}
|
|
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; },
|
|
291
|
+
stopTrial() { trial?.stop(); },
|
|
292
|
+
trialSnapshot() { return { rows: registry?.snapshot().filter(row => trial?.binding(row.key, row.id)) ?? [] }; },
|
|
293
|
+
cursorSelection: () => personal?.cursorSelection() ?? null, cursorImageSettled,
|
|
294
|
+
pauseOwner(slot) { return personal?.pause(slot) === true; },
|
|
295
|
+
resumeOwner(slot) { if (!personal?.unpause(slot)) return false; const b = personal.slotBinding(slot); return bindingAlive(b) && registry.resume(b.key); },
|
|
296
|
+
changedChat() { changedSource(source); }, reply(event) { publishReply(source, event); }, detachChat() { discard(source); sync(); }, snapshot: () => snapshot(source), dispose };
|
|
297
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** #1629: host cadence before ANY effect frame; never learns a baseline from an active effect. */
|
|
2
|
+
const finite = n => typeof n === "number" && Number.isFinite(n);
|
|
3
|
+
export function captureEffectBaseline({ window: host, empty, clock = () => host.performance.now() } = {}) {
|
|
4
|
+
let finish, settled = false, raf = null, timer = null, last = null, first = null, count = 0;
|
|
5
|
+
const promise = new Promise(resolve => { finish = resolve; });
|
|
6
|
+
const end = value => {
|
|
7
|
+
if (settled) return;
|
|
8
|
+
settled = true;
|
|
9
|
+
if (raf !== null) host.cancelAnimationFrame(raf);
|
|
10
|
+
if (timer !== null) host.clearTimeout(timer);
|
|
11
|
+
host.document.removeEventListener("visibilitychange", visibility);
|
|
12
|
+
finish(value);
|
|
13
|
+
};
|
|
14
|
+
const fail = reason => end({ ok: false, reason });
|
|
15
|
+
const gates = () => {
|
|
16
|
+
try { return host.document.visibilityState === "visible" && empty() === true; } catch { return false; }
|
|
17
|
+
};
|
|
18
|
+
const time = () => {
|
|
19
|
+
let now; try { now = clock(); } catch { return null; }
|
|
20
|
+
if (!finite(now) || now < 0 || last !== null && now < last) return null;
|
|
21
|
+
last = now; return now;
|
|
22
|
+
};
|
|
23
|
+
const visibility = () => { if (!gates()) fail("baseline_unavailable"); };
|
|
24
|
+
const step = () => {
|
|
25
|
+
if (settled) return;
|
|
26
|
+
raf = null;
|
|
27
|
+
if (!gates()) { fail("baseline_unavailable"); return; }
|
|
28
|
+
const now = time();
|
|
29
|
+
if (now === null) { fail("clock_unavailable"); return; }
|
|
30
|
+
if (now - began >= 10000) { fail("baseline_timeout"); return; }
|
|
31
|
+
if (first === null) { first = now; count = 1; }
|
|
32
|
+
else if (now > sample) count++;
|
|
33
|
+
sample = now;
|
|
34
|
+
if (now - first >= 2000) {
|
|
35
|
+
const fps = (count - 1) * 1000 / (now - first);
|
|
36
|
+
if (!finite(fps) || fps <= 0 || fps > 1000 || !gates()) fail("baseline_unavailable");
|
|
37
|
+
else end({ ok: true, baselineFps: fps });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
raf = host.requestAnimationFrame(step);
|
|
41
|
+
};
|
|
42
|
+
let began = null, sample = null;
|
|
43
|
+
if (!host || !host.document || typeof empty !== "function" || typeof clock !== "function" || ["requestAnimationFrame", "cancelAnimationFrame", "setTimeout", "clearTimeout"].some(k => typeof host[k] !== "function") || typeof host.document.addEventListener !== "function" || typeof host.document.removeEventListener !== "function") {
|
|
44
|
+
settled = true; finish({ ok: false, reason: "baseline_unsupported" });
|
|
45
|
+
return { promise, cancel() {} };
|
|
46
|
+
}
|
|
47
|
+
began = time();
|
|
48
|
+
if (began === null) fail("clock_unavailable");
|
|
49
|
+
else if (!gates()) fail("baseline_unavailable");
|
|
50
|
+
else {
|
|
51
|
+
host.document.addEventListener("visibilitychange", visibility);
|
|
52
|
+
const deadline = () => {
|
|
53
|
+
if (settled) return;
|
|
54
|
+
if (!gates()) { fail("baseline_unavailable"); return; }
|
|
55
|
+
const now = time();
|
|
56
|
+
if (now === null) fail("clock_unavailable");
|
|
57
|
+
else if (now - began >= 10000) fail("baseline_timeout");
|
|
58
|
+
else timer = host.setTimeout(deadline, Math.max(1, 10000 - (now - began)));
|
|
59
|
+
};
|
|
60
|
+
timer = host.setTimeout(deadline, 10000);
|
|
61
|
+
raf = host.requestAnimationFrame(step);
|
|
62
|
+
}
|
|
63
|
+
return { promise, cancel() { fail("baseline_cancelled"); } };
|
|
64
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/** #1629: one committed local Chat lease joins the reviewed host-only effect primitives. */
|
|
2
|
+
import { createEffectRuntimeLoader } from "./effect-runtime-loader.js";
|
|
3
|
+
import { createEffectRegistry } from "./effect-registry.js";
|
|
4
|
+
import { createEffectSafety } from "./effect-safety.js";
|
|
5
|
+
import { stockEffectTrust } from "./effect-stock-trust.js";
|
|
6
|
+
import { createEffectProfileCollector } from "./effect-profiles.js";
|
|
7
|
+
|
|
8
|
+
const writers = new WeakMap();
|
|
9
|
+
/** Physical write reservations outlive a canceled caller. Responses never supply runtime authority. */
|
|
10
|
+
export function createEffectWriter(host, current) {
|
|
11
|
+
let dead = false;
|
|
12
|
+
const own = new Set();
|
|
13
|
+
if (!writers.has(host)) writers.set(host, new Set());
|
|
14
|
+
const all = writers.get(host);
|
|
15
|
+
let previous = null;
|
|
16
|
+
function stop() { if (dead) return; dead = true; for (const op of own) op.cancel(); }
|
|
17
|
+
function time() {
|
|
18
|
+
let now; try { now = host.performance.now(); } catch { stop(); return null; }
|
|
19
|
+
if (!Number.isFinite(now) || now < 0 || previous !== null && now < previous) { stop(); return null; }
|
|
20
|
+
previous = now; return now;
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
async post(path, body) {
|
|
24
|
+
if (dead || !current() || all.size >= 3) return false;
|
|
25
|
+
const began = time(); if (began === null) return false;
|
|
26
|
+
const text = JSON.stringify(body); if (new TextEncoder().encode(text).length > 16384) return false;
|
|
27
|
+
const controller = new host.AbortController(), op = { controller }; own.add(op); all.add(op);
|
|
28
|
+
let timer, cancel;
|
|
29
|
+
const aborted = new Promise(resolve => { cancel = () => { controller.abort(); resolve(false); }; }); op.cancel = cancel;
|
|
30
|
+
const work = Promise.resolve().then(async () => {
|
|
31
|
+
if (dead || !current() || controller.signal.aborted) return false;
|
|
32
|
+
const r = await host.fetch(path, { method: "POST", credentials: "same-origin", cache: "no-store", redirect: "error", headers: { "Content-Type": "application/json" }, body: text, signal: controller.signal });
|
|
33
|
+
// Settle the body before returning the physical reservation, even if the caller already timed out.
|
|
34
|
+
try { await r.body?.cancel(); } catch { /* no data is consumed */ }
|
|
35
|
+
const now = time();
|
|
36
|
+
return !dead && current() && !controller.signal.aborted && Number.isFinite(began) && began >= 0 && now !== null && now >= began && now - began < 10000 && r.ok && !r.redirected;
|
|
37
|
+
}).catch(() => false).finally(() => { host.clearTimeout(timer); controller.abort(); own.delete(op); all.delete(op); });
|
|
38
|
+
timer = host.setTimeout(cancel, 10000);
|
|
39
|
+
return Promise.race([work, aborted]);
|
|
40
|
+
},
|
|
41
|
+
dispose: stop,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Frozen, server-rendered session identity only. A header-token/public/View load has no identity. */
|
|
46
|
+
export function chatEffectIdentity(host) {
|
|
47
|
+
try {
|
|
48
|
+
const b = host.document.body.dataset, x = JSON.parse(host.document.documentElement.getAttribute("data-prefs") || "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
|
+
return Object.freeze({ epoch: x.ownerEpoch, scope: x.ownerScope });
|
|
51
|
+
} catch { return null; }
|
|
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
|
+
}
|
|
66
|
+
export function chatEffectState(state, name) {
|
|
67
|
+
const inst = state.instances?.find(i => i.name === name);
|
|
68
|
+
if (!inst) return null;
|
|
69
|
+
if (["stopped", "paused"].includes(inst.status)) return "stopped";
|
|
70
|
+
if (Object.hasOwn(state.awaiting ?? {}, name)) return "needs_you";
|
|
71
|
+
return ["working", "stuck"].includes(state.exec?.[name]) ? "working" : state.exec?.[name] === "idle" ? "idle" : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** `observe` supplies closed host gates, never event/message content. No timers poll server state. */
|
|
75
|
+
export function createChatEffectRuntime({ window: host, name, element, current, observe, onChange = () => {}, factories = {} }) {
|
|
76
|
+
let dead = false, error = null, loader = null, registry = null, writer = null, active = null, queued = false, generation = 0, metadata = null, ctx = null, lastContext = null;
|
|
77
|
+
const paused = new Map(), owners = new Map();
|
|
78
|
+
const placementIdentity = p => JSON.stringify([p.by, p.manifest.layer, p.params]);
|
|
79
|
+
const identity = chatEffectIdentity(host), auth = host.AgendAuth;
|
|
80
|
+
const alive = () => !dead && current() === true && element.isConnected === true && !auth.sessionEnded();
|
|
81
|
+
const tell = () => { if (!dead && current()) onChange({ error, rows: registry?.snapshot() ?? [], effects: metadata?.effects ?? [], loading: metadata === null && !error }); };
|
|
82
|
+
const fail = reason => { if (dead) return; error = reason; generation++; metadata = null; active?.controller.abort(); registry?.dispose?.(); registry = null; loader?.dispose?.(); loader = null; writer?.dispose(); tell(); };
|
|
83
|
+
function dispose() {
|
|
84
|
+
if (dead) return; dead = true; generation++; active?.controller.abort(); registry?.dispose?.(); loader?.dispose?.(); writer?.dispose(); offAuth?.();
|
|
85
|
+
for (const value of owners.values()) value.collector.dispose(); owners.clear();
|
|
86
|
+
}
|
|
87
|
+
let offAuth = null;
|
|
88
|
+
if (!identity || typeof auth?.onSessionEnd !== "function" || typeof auth?.sessionEnded !== "function" || typeof name !== "string" || name.length < 1 || name.length > 128 || !current()) return { ok: false, dispose };
|
|
89
|
+
offAuth = auth.onSessionEnd(dispose); // May synchronously replay an already-ended episode.
|
|
90
|
+
if (dead || !alive()) { dispose(); return { ok: false, dispose }; }
|
|
91
|
+
const clock = () => host.performance.now();
|
|
92
|
+
try {
|
|
93
|
+
const make = (key, fn) => factories[key] ?? fn;
|
|
94
|
+
writer = createEffectWriter(host, alive);
|
|
95
|
+
loader = make("loader", createEffectRuntimeLoader)({ window: host, ownerEpoch: identity.epoch, current: alive });
|
|
96
|
+
if (!loader.ok) throw Error("runtime_unavailable");
|
|
97
|
+
registry = make("registry", createEffectRegistry)({ window: host, origin: host.location?.origin, boot: host.document.body.dataset.effectBoot,
|
|
98
|
+
load: async (d, signal) => {
|
|
99
|
+
const mine = generation, value = await loader.resource({ id: d.id, instance: name }, signal);
|
|
100
|
+
if (!alive() || mine !== generation || signal.aborted) return null;
|
|
101
|
+
if (!value) { fail("resource_unavailable"); return null; }
|
|
102
|
+
return value;
|
|
103
|
+
},
|
|
104
|
+
safety: make("safety", createEffectSafety)({ storage: host.localStorage, crypto: host.crypto }),
|
|
105
|
+
stockSnapshot: ({ frameRoot }) => make("stock", stockEffectTrust)(host, { frameRoot, ownerCss: observe().ownerCss }),
|
|
106
|
+
onChange() { tell(); emit(); },
|
|
107
|
+
onMount(owner) {
|
|
108
|
+
if (!alive() || !owner.current()) return;
|
|
109
|
+
const collector = make("collector", createEffectProfileCollector)({ key: owner.key, id: owner.id, device: owner.device, clock });
|
|
110
|
+
if (collector.ok) owners.set(owner, { collector, began: clock(), attempted: null });
|
|
111
|
+
},
|
|
112
|
+
onRetire(owner) { const row = owners.get(owner); row?.collector.dispose(); owners.delete(owner); },
|
|
113
|
+
onProfile(raw, owner) {
|
|
114
|
+
const row = owners.get(owner); if (!row || !alive() || !owner.current()) return;
|
|
115
|
+
row.collector.add(raw);
|
|
116
|
+
const now = clock();
|
|
117
|
+
if (now - row.began < 10000 && raw.verdict !== "paused" || row.attempted !== null && now - row.attempted < 10000) return;
|
|
118
|
+
const report = row.collector.report(); if (!report || !owner.current()) return;
|
|
119
|
+
row.attempted = now;
|
|
120
|
+
void writer.post(`/ui/effects/profile/${owner.id}?instance=${encodeURIComponent(name)}&ownerEpoch=${identity.epoch}`, report.profile);
|
|
121
|
+
}, clock,
|
|
122
|
+
});
|
|
123
|
+
if (!registry.ok) throw Error("runtime_unavailable");
|
|
124
|
+
} catch { fail("runtime_unavailable"); }
|
|
125
|
+
function descriptors() {
|
|
126
|
+
return (metadata?.effects ?? []).filter(p => p.manifest.regions.includes("chat")).map((p, order) => ({ key: `chat:${p.id}`, id: p.id, by: p.by, region: "chat", layer: p.manifest.layer,
|
|
127
|
+
order, paused: paused.get(p.id) === placementIdentity(p), visible: true, instance: name, params: p.params, element }));
|
|
128
|
+
}
|
|
129
|
+
function emit() {
|
|
130
|
+
if (!alive() || error || !registry?.ok) return;
|
|
131
|
+
const value = observe();
|
|
132
|
+
if (["idle", "working", "needs_you", "stopped"].includes(value.state)) registry.publish("state", { state: value.state }, name);
|
|
133
|
+
if (["light", "dark"].includes(value.mode)) registry.publish("theme", { mode: value.mode });
|
|
134
|
+
}
|
|
135
|
+
function sync() {
|
|
136
|
+
if (!alive()) { dispose(); return; } if (error) return;
|
|
137
|
+
const value = observe(); ctx = value.context;
|
|
138
|
+
const stamp = JSON.stringify(ctx);
|
|
139
|
+
if (stamp !== lastContext) { lastContext = stamp; registry.update(descriptors(), ctx); }
|
|
140
|
+
emit();
|
|
141
|
+
if (!metadata && !active && ctx.enabled && ctx.show && ctx.transportReady && !ctx.safe && !ctx.publicLink && !ctx.reducedMotion && !ctx.hidden) void read();
|
|
142
|
+
}
|
|
143
|
+
async function read() {
|
|
144
|
+
if (!alive() || error || active) return;
|
|
145
|
+
const op = { controller: new host.AbortController(), generation }; active = op;
|
|
146
|
+
try {
|
|
147
|
+
const answer = await loader.placements(name, op.controller.signal);
|
|
148
|
+
if (!alive() || active !== op) return;
|
|
149
|
+
if (op.generation !== generation) return;
|
|
150
|
+
if (!answer) { fail("resource_unavailable"); return; }
|
|
151
|
+
metadata = answer;
|
|
152
|
+
for (const [id, stamp] of paused) if (!answer.effects.some(p => p.id === id && placementIdentity(p) === stamp)) paused.delete(id);
|
|
153
|
+
lastContext = null; sync(); tell();
|
|
154
|
+
} finally {
|
|
155
|
+
if (active === op) active = null;
|
|
156
|
+
if (queued && alive() && !error) { queued = false; sync(); }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { ok: !error && !dead, sync,
|
|
160
|
+
changed() { if (!alive() || error) return; generation++; metadata = null; registry.update([], observe().context); lastContext = null; if (active) queued = true; else sync(); tell(); },
|
|
161
|
+
reply(event) { if (alive() && !error && event.instance === name) registry.publish("reply", {}, name); },
|
|
162
|
+
pause(id) { if (!alive() || !metadata?.effects.some(p => p.id === id)) return false; paused.set(id, placementIdentity(metadata.effects.find(p => p.id === id))); lastContext = null; sync(); return true; },
|
|
163
|
+
resume(id) { if (!alive() || error || !metadata?.effects.some(p => p.id === id)) return false; paused.delete(id); lastContext = null; sync(); return registry.resume(`chat:${id}`); },
|
|
164
|
+
async remove(id) { if (!alive() || error || !metadata?.effects.some(p => p.id === id)) return false; const mine = generation; const ok = await writer.post("/ui/effects/remove", { instance: name, id }); if (!alive() || mine !== generation) return false; if (ok) this.changed(); return ok; },
|
|
165
|
+
snapshot: () => ({ error, rows: registry?.snapshot() ?? [], effects: metadata?.effects ?? [], loading: metadata === null && !error }), dispose,
|
|
166
|
+
};
|
|
167
|
+
}
|