@songsid/agend 2.3.2 → 2.3.3-beta.10
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 +2 -2
- package/dist/agent-cli.js +13 -112
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +0 -1
- package/dist/agent-endpoint.js +1 -9
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +23 -5
- package/dist/agent-whats-new.js +58 -40
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +0 -7
- package/dist/auth-api.js +1 -15
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/authority-state.js +1 -1
- package/dist/authority/authority-state.js.map +1 -1
- package/dist/authority/web-authority.d.ts +9 -4
- package/dist/authority/web-authority.js +10 -5
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/backend/antigravity.js +6 -1
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +20 -1
- package/dist/backend/claude-code.js +54 -29
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/claude-transcript-errors.d.ts +18 -0
- package/dist/backend/claude-transcript-errors.js +19 -0
- package/dist/backend/claude-transcript-errors.js.map +1 -0
- package/dist/backend/codex-session-lookup.d.ts +16 -3
- package/dist/backend/codex-session-lookup.js +45 -11
- package/dist/backend/codex-session-lookup.js.map +1 -1
- package/dist/backend/codex.d.ts +16 -0
- package/dist/backend/codex.js +184 -35
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.js +58 -12
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro-reconcile-store.js +8 -1
- package/dist/backend/kiro-reconcile-store.js.map +1 -1
- package/dist/backend/kiro.d.ts +0 -5
- package/dist/backend/kiro.js +2 -10
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.js +2 -1
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +53 -4
- package/dist/backend/types.js.map +1 -1
- package/dist/bundled-skills.d.ts +4 -6
- package/dist/bundled-skills.js +10 -14
- package/dist/bundled-skills.js.map +1 -1
- package/dist/channel/adapters/discord.js +60 -17
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-server.js +5 -5
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +1 -3
- package/dist/channel/mcp-tools.js +4 -58
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/classic-channel-manager.d.ts +4 -1
- package/dist/classic-channel-manager.js +21 -7
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +23 -6
- package/dist/cli.js.map +1 -1
- package/dist/config-history.d.ts +50 -0
- package/dist/config-history.js +263 -0
- package/dist/config-history.js.map +1 -0
- package/dist/config-validator.js +10 -8
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +11 -4
- package/dist/config.js.map +1 -1
- package/dist/cross-instance-envelope.d.ts +10 -5
- package/dist/cross-instance-envelope.js +20 -17
- package/dist/cross-instance-envelope.js.map +1 -1
- package/dist/daemon.d.ts +130 -64
- package/dist/daemon.js +603 -428
- package/dist/daemon.js.map +1 -1
- package/dist/daily-summary.d.ts +8 -1
- package/dist/daily-summary.js +8 -5
- package/dist/daily-summary.js.map +1 -1
- package/dist/dashboard-live.d.ts +1 -1
- package/dist/dashboard-live.js +3 -3
- package/dist/dashboard-live.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +1 -1
- package/dist/dashboard-permissions.js +1 -3
- package/dist/dashboard-permissions.js.map +1 -1
- package/dist/delivery-outbox.d.ts +31 -22
- package/dist/delivery-outbox.js +119 -161
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-outbox.test.js +10 -25
- package/dist/delivery-outbox.test.js.map +1 -1
- package/dist/doctor.d.ts +4 -0
- package/dist/doctor.js +16 -1
- package/dist/doctor.js.map +1 -1
- package/dist/fleet-level-config.js +1 -1
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +74 -66
- package/dist/fleet-manager.js +285 -414
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-process-handlers.d.ts +5 -0
- package/dist/fleet-process-handlers.js +21 -0
- package/dist/fleet-process-handlers.js.map +1 -1
- package/dist/fleet-service-owner.d.ts +11 -0
- package/dist/fleet-service-owner.js +3 -0
- package/dist/fleet-service-owner.js.map +1 -0
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +11 -1
- package/dist/general-knowledge/skills/delegation-playbook/SKILL.md +1 -1
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +1 -1
- package/dist/general-knowledge/skills/session-management/SKILL.md +7 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +13 -204
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +30 -68
- package/dist/instance-lifecycle.d.ts +19 -0
- package/dist/instance-lifecycle.js +73 -15
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.js +6 -7
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-reconcile.js +3 -3
- package/dist/kiro-reconcile.js.map +1 -1
- package/dist/locale.js +96 -58
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +1 -1
- package/dist/log-service.js.map +1 -1
- package/dist/needs-you-hub.d.ts +1 -0
- package/dist/needs-you-hub.js +6 -3
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +3 -1
- package/dist/needs-you-live.js +24 -2
- package/dist/needs-you-live.js.map +1 -1
- package/dist/needs-you.d.ts +13 -2
- package/dist/needs-you.js +11 -1
- package/dist/needs-you.js.map +1 -1
- package/dist/notify-socket.d.ts +10 -0
- package/dist/notify-socket.js +45 -0
- package/dist/notify-socket.js.map +1 -0
- package/dist/outbound-handlers.js +20 -8
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +0 -62
- package/dist/outbound-schemas.js +4 -43
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/parked-notices.d.ts +87 -0
- package/dist/parked-notices.js +194 -0
- package/dist/parked-notices.js.map +1 -0
- package/dist/public-link-progress.d.ts +5 -2
- package/dist/public-link-progress.js +4 -3
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-web-gateway.js +0 -9
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +12 -0
- package/dist/public-web-link.js +29 -5
- package/dist/public-web-link.js.map +1 -1
- package/dist/quota-notice.d.ts +23 -0
- package/dist/quota-notice.js +22 -0
- package/dist/quota-notice.js.map +1 -0
- package/dist/scheduler/task-board.d.ts +17 -25
- package/dist/scheduler/task-board.js +29 -39
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/sd-notify.d.ts +23 -15
- package/dist/sd-notify.js +88 -29
- package/dist/sd-notify.js.map +1 -1
- package/dist/search/search-docs.d.ts +1 -1
- package/dist/search/search-docs.js +2 -2
- package/dist/search/search-docs.js.map +1 -1
- package/dist/settings-api.d.ts +16 -1
- package/dist/settings-api.js +56 -16
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +3 -1
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +17 -3
- package/dist/settings-confirmation.js +8 -5
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-effect.js +0 -2
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +31 -1
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/setup-wizard.d.ts +3 -0
- package/dist/setup-wizard.js +4 -2
- package/dist/setup-wizard.js.map +1 -1
- package/dist/task-board-waker.d.ts +43 -1
- package/dist/task-board-waker.js +121 -9
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tmux-control.d.ts +1 -1
- package/dist/tmux-control.js +3 -2
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-manager.js +18 -16
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tmux-read.d.ts +2 -0
- package/dist/tmux-read.js +7 -1
- package/dist/tmux-read.js.map +1 -1
- package/dist/tool-permissions.js +3 -10
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +29 -7
- package/dist/topic-commands.js +97 -37
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-monitor.d.ts +7 -0
- package/dist/transcript-monitor.js +48 -3
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +5 -0
- package/dist/tunnel/cloudflared.js +32 -1
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/named-diagnostics.d.ts +2 -1
- package/dist/tunnel/named-diagnostics.js +4 -1
- package/dist/tunnel/named-diagnostics.js.map +1 -1
- package/dist/tunnel/named-link-test.d.ts +90 -0
- package/dist/tunnel/named-link-test.js +270 -0
- package/dist/tunnel/named-link-test.js.map +1 -0
- package/dist/tunnel/purpose-lane.d.ts +3 -1
- package/dist/tunnel/purpose-lane.js +2 -0
- package/dist/tunnel/purpose-lane.js.map +1 -1
- package/dist/types.d.ts +9 -11
- package/dist/ui/chat-ambient.js +4 -4
- package/dist/ui/chat-commands.js +3 -2
- package/dist/ui/chat-store.js +4 -1
- package/dist/ui/chat-strings.js +12 -46
- package/dist/ui/chat-thread.js +51 -59
- package/dist/ui/fleet-office-skin.js +37 -14
- package/dist/ui/fleet-office.js +31 -7
- package/dist/ui/html-attachment.js +22 -8
- package/dist/ui/panel-chat.js +21 -50
- package/dist/ui/panel-log.js +2 -2
- package/dist/ui/panel-needs.js +11 -9
- package/dist/ui/panel-settings.js +5 -3
- package/dist/ui/preview-panel.js +12 -21
- package/dist/ui/preview.js +10 -3
- package/dist/ui/search-dialog.js +30 -14
- package/dist/ui/settings-appearance.js +3 -37
- package/dist/ui/settings-confirm.js +18 -0
- package/dist/ui/settings-guests.js +13 -4
- package/dist/ui/settings-learn.js +1 -1
- package/dist/ui/settings-mods.js +5 -12
- package/dist/ui/settings-named-test.js +144 -0
- package/dist/ui/settings-registry.js +0 -2
- package/dist/ui/settings-strings.js +16 -16
- package/dist/ui/settings-web-authority.js +4 -3
- package/dist/ui/shared/app-i18n.js +15 -4
- package/dist/ui/shared/app-needs.js +5 -1
- package/dist/ui/shared/app-prefs.js +17 -17
- package/dist/ui/shared/app-shell.js +47 -9
- package/dist/ui/shared/app-store.js +6 -2
- package/dist/ui/shared/app.css +33 -39
- package/dist/ui/shared/app.js +5 -4
- package/dist/ui/shared/instance-nav.js +9 -9
- package/dist/ui/shared/instance-switch.js +8 -2
- package/dist/ui/shared/panel-view.js +22 -8
- package/dist/ui/shared/view-strings.js +6 -0
- package/dist/ui/shared/web-authorize.js +35 -13
- package/dist/update-check.d.ts +13 -4
- package/dist/update-check.js +17 -4
- package/dist/update-check.js.map +1 -1
- package/dist/usage/usage-api.d.ts +3 -0
- package/dist/usage/usage-api.js +5 -1
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/view-api.d.ts +2 -0
- package/dist/view-api.js +64 -0
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +9 -17
- package/dist/web-api.js +257 -371
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +0 -2
- package/dist/web-appearance-tools.js +2 -5
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.d.ts +0 -6
- package/dist/web-appearance-view.js +0 -1
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.d.ts +14 -7
- package/dist/web-appearance.js +5 -6
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-asset-parts.d.ts +1 -17
- package/dist/web-asset-parts.js +1 -15
- package/dist/web-asset-parts.js.map +1 -1
- package/dist/web-auth.js +1 -7
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +3 -7
- package/dist/web-chat-history.js +10 -17
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +9 -1
- package/dist/web-commands.js +13 -0
- package/dist/web-commands.js.map +1 -1
- package/dist/web-experimental.d.ts +3 -8
- package/dist/web-experimental.js +9 -33
- package/dist/web-experimental.js.map +1 -1
- package/dist/web-guest-access.d.ts +40 -2
- package/dist/web-guest-access.js +50 -14
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-mod-bundle.d.ts +2 -45
- package/dist/web-mod-bundle.js +15 -63
- package/dist/web-mod-bundle.js.map +1 -1
- package/dist/web-mod-store.d.ts +3 -48
- package/dist/web-mod-store.js +30 -97
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-prefs.d.ts +13 -0
- package/dist/web-prefs.js +38 -0
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +6 -13
- package/dist/web-preview.js +12 -23
- package/dist/web-preview.js.map +1 -1
- package/dist/web-session.d.ts +11 -3
- package/dist/web-session.js +22 -11
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +1 -1
- package/dist/web-settings-patch.js +9 -7
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +0 -13
- package/dist/web-settings-tools.js +12 -84
- package/dist/web-settings-tools.js.map +1 -1
- package/dist/workflow-templates/default.md +2 -2
- package/dist/working-recovery.d.ts +0 -2
- package/dist/working-recovery.js +2 -23
- package/dist/working-recovery.js.map +1 -1
- package/launcher/agend-install.cjs +7 -1
- package/launcher/fleet-service-owner.cjs +150 -0
- package/package.json +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +0 -376
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +0 -673
- package/dist/task-board-summary.d.ts +0 -101
- package/dist/task-board-summary.js +0 -177
- package/dist/task-board-summary.js.map +0 -1
- package/dist/ui/app-effects.js +0 -251
- package/dist/ui/chat-effect-posters.js +0 -42
- package/dist/ui/chat-effects.js +0 -167
- package/dist/ui/effect-trial-controller.js +0 -124
- package/dist/ui/effect-trial-offer.js +0 -2
- package/dist/ui/effect-trial-panel.js +0 -40
- package/dist/ui/effects-waker.js +0 -2
- package/dist/ui/settings-effects.js +0 -202
- package/dist/ui/settings-owner-effects.js +0 -82
- package/dist/ui/shared/effect-app-runtime.js +0 -299
- package/dist/ui/shared/effect-baseline.js +0 -64
- package/dist/ui/shared/effect-chat-runtime.js +0 -162
- package/dist/ui/shared/effect-cursor.js +0 -126
- package/dist/ui/shared/effect-display-metadata.js +0 -39
- package/dist/ui/shared/effect-frame.js +0 -146
- package/dist/ui/shared/effect-geometry.js +0 -137
- package/dist/ui/shared/effect-host-style.js +0 -10
- package/dist/ui/shared/effect-owner-regions.js +0 -74
- package/dist/ui/shared/effect-owner-source.js +0 -122
- package/dist/ui/shared/effect-policy.js +0 -151
- package/dist/ui/shared/effect-poster-reader.js +0 -98
- package/dist/ui/shared/effect-posters.js +0 -117
- package/dist/ui/shared/effect-profiler.js +0 -184
- package/dist/ui/shared/effect-profiles.js +0 -65
- package/dist/ui/shared/effect-registry.js +0 -545
- package/dist/ui/shared/effect-runtime-loader.js +0 -236
- package/dist/ui/shared/effect-safety.js +0 -153
- package/dist/ui/shared/effect-stock-snapshot.js +0 -43
- package/dist/ui/shared/effect-stock-trust.js +0 -217
- package/dist/ui/shared/effect-trial-shadow.js +0 -62
- package/dist/ui/shared/effect-trial-source.js +0 -103
- package/dist/ui/shared/effect-trial-stage.js +0 -170
- package/dist/ui/shared/effect-trial.css +0 -40
- package/dist/ui/shared/feature-flags.js +0 -127
- package/dist/ui/shared/owner-effect-prefs.js +0 -117
- package/dist/upgrade-notices.d.ts +0 -17
- package/dist/upgrade-notices.js +0 -93
- package/dist/upgrade-notices.js.map +0 -1
- package/dist/web-effect-bootstrap.d.ts +0 -12
- package/dist/web-effect-bootstrap.js +0 -49
- package/dist/web-effect-bootstrap.js.map +0 -1
- package/dist/web-effect-bundle.d.ts +0 -28
- package/dist/web-effect-bundle.js +0 -171
- package/dist/web-effect-bundle.js.map +0 -1
- package/dist/web-effect-canonical.d.ts +0 -8
- package/dist/web-effect-canonical.js +0 -27
- package/dist/web-effect-canonical.js.map +0 -1
- package/dist/web-effect-library.d.ts +0 -114
- package/dist/web-effect-library.js +0 -530
- package/dist/web-effect-library.js.map +0 -1
- package/dist/web-effect-playback.d.ts +0 -30
- package/dist/web-effect-playback.js +0 -71
- package/dist/web-effect-playback.js.map +0 -1
- package/dist/web-effect-poster.d.ts +0 -8
- package/dist/web-effect-poster.js +0 -52
- package/dist/web-effect-poster.js.map +0 -1
- package/dist/web-effect-profiles.d.ts +0 -118
- package/dist/web-effect-profiles.js +0 -181
- package/dist/web-effect-profiles.js.map +0 -1
- package/dist/web-effect-service.d.ts +0 -93
- package/dist/web-effect-service.js +0 -335
- package/dist/web-effect-service.js.map +0 -1
- package/dist/web-effect-tools.d.ts +0 -48
- package/dist/web-effect-tools.js +0 -129
- package/dist/web-effect-tools.js.map +0 -1
- package/dist/web-effect-trials.d.ts +0 -86
- package/dist/web-effect-trials.js +0 -204
- package/dist/web-effect-trials.js.map +0 -1
- package/dist/web-effect-upload.d.ts +0 -27
- package/dist/web-effect-upload.js +0 -185
- package/dist/web-effect-upload.js.map +0 -1
- package/dist/web-effect-worker.d.ts +0 -8
- package/dist/web-effect-worker.js +0 -170
- package/dist/web-effect-worker.js.map +0 -1
- package/dist/web-effect.d.ts +0 -103
- package/dist/web-effect.js +0 -153
- package/dist/web-effect.js.map +0 -1
- package/dist/web-experimental-http.d.ts +0 -2
- package/dist/web-experimental-http.js +0 -12
- package/dist/web-experimental-http.js.map +0 -1
- package/dist/web-mod-agent.d.ts +0 -50
- package/dist/web-mod-agent.js +0 -207
- package/dist/web-mod-agent.js.map +0 -1
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
// #1629: authoritative selectors and local slot controls; the app owns all playback and resource reads.
|
|
2
|
-
import { html, useEffect, useRef, useState } from "/assets/app-html.js";
|
|
3
|
-
import { register, t } from "/assets/app-i18n.js";
|
|
4
|
-
import { useStore } from "/assets/app-store.js";
|
|
5
|
-
import { OWNER_EFFECT_PREF_KEYS } from "/assets/app-prefs.js";
|
|
6
|
-
import { getAppEffects, wakeEffects } from "./effects-waker.js";
|
|
7
|
-
import { ownerEffectsStore, watchOwnerEffects, refreshOwnerEffects, saveOwnerEffects } from "/assets/owner-effect-prefs.js";
|
|
8
|
-
register("ownerEffects", {
|
|
9
|
-
en: {
|
|
10
|
-
title: "Your effect choices", operatorTitle: "Host-wide effect choices", person: "Follows your sign-in; Discord and Telegram are separate.",
|
|
11
|
-
operator: "Shared by everyone signed in as the host.",
|
|
12
|
-
sidebar: "Sidebar", header: "Header", page: "Page", box: "Box", cursor: "Cursor", none: "None", savedChoice: "Saved choice",
|
|
13
|
-
unknown: "Choices are unavailable. Reload to try again.", changed: "This page can no longer access these choices. Reload and sign in again.",
|
|
14
|
-
unavailable: "No longer available", clear: "Clear", reload: "Reload choices", saved: "Saved.", failed: "Couldn't save. Reload choices and try again.",
|
|
15
|
-
savedRefresh: "Saved, but the choices couldn't be refreshed. Reload to check.", readOnly: "Sign in as an admin to change these choices.",
|
|
16
|
-
playback: "Effects don't require HTML-preview permission.",
|
|
17
|
-
pause: "Pause", resume: "Resume", running: "Running", cursorFailed: "The cursor could not be applied here. Clear or reload the choice to try again.", starting: "Starting", queued: "Waiting for a slot", paused: "Paused", off: "Playback is off or unavailable here.", budget: "Paused to keep the page responsive.", resumeFailed: "This effect cannot resume: the page or its safety history is not ready. Reload the page or choose another effect.",
|
|
18
|
-
},
|
|
19
|
-
"zh-TW": {
|
|
20
|
-
title: "你的效果選擇", operatorTitle: "主機共用效果選擇", person: "跟隨你的登入身分;Discord 與 Telegram 各自獨立。",
|
|
21
|
-
operator: "所有以主機身分登入的人共用。",
|
|
22
|
-
sidebar: "側邊欄", header: "頁首", page: "頁面", box: "區塊", cursor: "游標", none: "無", savedChoice: "已儲存的選擇",
|
|
23
|
-
unknown: "目前無法取得選擇,請重新載入再試。", changed: "這個頁面已無法存取選擇,請重新載入並登入。",
|
|
24
|
-
unavailable: "已無法使用", clear: "清除", reload: "重新載入選擇", saved: "已儲存。", failed: "無法儲存,請重新載入選擇再試。",
|
|
25
|
-
savedRefresh: "已儲存,但無法更新畫面;請重新載入確認。", readOnly: "請以管理員身分登入才能修改選擇。",
|
|
26
|
-
playback: "效果不需要 HTML 預覽權限。",
|
|
27
|
-
pause: "暫停", resume: "恢復", running: "播放中", cursorFailed: "這裡無法套用游標。可清除或重新載入選擇後再試。", starting: "啟動中", queued: "等待空位", paused: "已暫停", off: "這裡已關閉播放,或無法播放效果。", budget: "為維持頁面順暢,已暫停。", resumeFailed: "頁面或安全紀錄尚未就緒,無法恢復此效果。請重新載入頁面,或選擇其他效果。",
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
const text = key => t(`ownerEffects.${key}`);
|
|
31
|
-
export function ownerEffectFits(manifest, key) {
|
|
32
|
-
if (!manifest || typeof manifest !== "object" || !OWNER_EFFECT_PREF_KEYS.includes(key)) return false;
|
|
33
|
-
if (key === "fx.cursor") return !!manifest.cursor;
|
|
34
|
-
const region = key.slice(3);
|
|
35
|
-
return Array.isArray(manifest.regions) && manifest.regions.includes(region) && (region !== "box" || !!manifest.box);
|
|
36
|
-
}
|
|
37
|
-
export function OwnerEffects({ catalog, busy = false }) {
|
|
38
|
-
const state = useStore(ownerEffectsStore), [message, setMessage] = useState(null), life = useRef({ alive: false, seq: 0 }), [runtime, setRuntime] = useState(null);
|
|
39
|
-
useEffect(() => { life.current.alive = true; const stop = watchOwnerEffects(); return () => { life.current.alive = false; life.current.seq++; stop(); }; }, []);
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
// #2193: managing personal effects needs the runtime's view of them: it is woken here (it may still be asleep).
|
|
42
|
-
let live = true, entry = getAppEffects(), off = null, end = null;
|
|
43
|
-
const pull = () => { if (!live) return; const gates = entry?.gates(); setRuntime({ entry, snapshot: entry?.api?.ownerSnapshot?.(), cursor: entry?.cursorSnapshot?.(), available: !!entry && !entry.dead && gates.enabled && gates.show && gates.transportReady && !gates.safe && !gates.publicLink && !gates.reducedMotion }); };
|
|
44
|
-
const bind = () => { off = entry?.subscribe(pull) ?? null; end = entry?.onEnd(pull) ?? null; pull(); };
|
|
45
|
-
if (entry) bind(); else { pull(); void wakeEffects().then(() => { if (!live) return; entry = getAppEffects(); bind(); }); }
|
|
46
|
-
return () => { live = false; off?.(); end?.(); };
|
|
47
|
-
}, []);
|
|
48
|
-
const reload = () => { getAppEffects()?.api?.changedOwner?.(); return refreshOwnerEffects(); };
|
|
49
|
-
// Library reloads/own share changes can alter availability without changing the preference revision.
|
|
50
|
-
useEffect(() => { if (catalog) reload(); }, [catalog]);
|
|
51
|
-
if (state.scope === "none") return null;
|
|
52
|
-
const disabled = busy || state.busy || state.status !== "known" || state.revoked || !catalog?.canWrite;
|
|
53
|
-
const save = async (key, id) => {
|
|
54
|
-
if (disabled || !life.current.alive) return;
|
|
55
|
-
if (id !== null && !(catalog.effects || []).some(e => e.id === id && ownerEffectFits(e.manifest, key))) return;
|
|
56
|
-
const seq = ++life.current.seq; setMessage(null);
|
|
57
|
-
const result = await saveOwnerEffects({ [key]: id });
|
|
58
|
-
if (!life.current.alive || seq !== life.current.seq) return;
|
|
59
|
-
if (result.ok || result.saved) getAppEffects()?.api?.changedOwner?.(); // #2193: asleep, the saved slot wakes it (app-prefs)
|
|
60
|
-
setMessage(text(result.ok ? "saved" : result.saved ? "savedRefresh" : "failed"));
|
|
61
|
-
};
|
|
62
|
-
return html`<section class="s-fx-owner" aria-label=${text(state.scope === "operator" ? "operatorTitle" : "title")}><h4>${text(state.scope === "operator" ? "operatorTitle" : "title")}</h4><p class="sub">${text(state.scope === "operator" ? "operator" : "person")}</p>
|
|
63
|
-
<p class="sub">${text("playback")}</p>${catalog && !catalog.canWrite ? html`<p class="sub">${text("readOnly")}</p>` : null}
|
|
64
|
-
<button class="btn" type="button" disabled=${busy || state.busy || state.revoked} onClick=${reload}>${text("reload")}</button>
|
|
65
|
-
${state.status !== "known" ? html`<p class="sub" role="status">${text(state.revoked ? "changed" : "unknown")}</p>` : OWNER_EFFECT_PREF_KEYS.map(key => {
|
|
66
|
-
const row = state.slots[key], options = (catalog?.effects || []).filter(e => ownerEffectFits(e.manifest, key));
|
|
67
|
-
const absent = !!row.id && !options.some(e => e.id === row.id);
|
|
68
|
-
const play = runtime?.snapshot?.rows?.find(r => r.slot === key && r.id === row.id);
|
|
69
|
-
const unavailable = row.status === "unavailable" || runtime?.snapshot?.slots?.[key]?.id === row.id && runtime.snapshot.slots[key].status === "unavailable";
|
|
70
|
-
const phase = !runtime?.available ? "off" : play?.reason === "paused" || play?.reason === "host_budget" ? "paused" : play?.phase === "running" ? "running" : play?.phase ? "starting" : play?.verdict === "queued" ? "queued" : "off";
|
|
71
|
-
return html`<div class="s-fx-owner-row" key=${key}><label>${text(key.slice(3))}<select class="s-fx-target" data-owner-slot=${key} disabled=${disabled} value=${row.id || ""} onChange=${e => save(key, e.target.value || null)}>
|
|
72
|
-
<option value="">${text("none")}</option>${absent ? html`<option value=${row.id} disabled>${text(row.status === "unavailable" ? "unavailable" : "savedChoice")}</option>` : null}
|
|
73
|
-
${options.map(e => html`<option key=${e.id} value=${e.id}>${e.manifest.title}</option>`)}</select></label>
|
|
74
|
-
${unavailable ? html`<span class="sub" role="status">${text("unavailable")}</span>` : null}
|
|
75
|
-
${key === "fx.cursor" && row.status === "available" && row.id && !unavailable ? html`<span class="sub" role="status" data-owner-runtime=${key}>${text(!runtime?.available ? "off" : runtime.cursor?.id === row.id && runtime.cursor.status === "running" ? "running" : runtime.cursor?.id === row.id && runtime.cursor.status === "loading" ? "starting" : runtime.cursor?.id === row.id && runtime.cursor.status === "unavailable" ? "cursorFailed" : "off")}</span>` : null}
|
|
76
|
-
${key !== "fx.cursor" && row.status === "available" && row.id && !unavailable ? html`<span class="sub" role="status" data-owner-runtime=${key}>${text(phase)}</span>
|
|
77
|
-
${play?.reason === "host_budget" ? html`<span class="sub">${text("budget")}</span>` : null}
|
|
78
|
-
<button class="btn" type="button" data-owner-pause=${key} disabled=${!runtime?.available || !play} onClick=${() => runtime?.entry?.api?.pauseOwner?.(key)}>${text("pause")}</button>
|
|
79
|
-
<button class="btn" type="button" data-owner-resume=${key} disabled=${!runtime?.available || !play} onClick=${() => { if (!runtime?.entry?.api?.resumeOwner?.(key)) setMessage(text("resumeFailed")); }}>${text("resume")}</button>` : null}
|
|
80
|
-
${row.id ? html`<button class="btn" type="button" data-owner-clear=${key} disabled=${disabled} onClick=${() => save(key, null)}>${text("clear")}</button>` : null}</div>`;
|
|
81
|
-
})}${message ? html`<p class="sub" role="status">${message}</p>` : null}</section>`;
|
|
82
|
-
}
|
|
@@ -1,299 +0,0 @@
|
|
|
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, claimed = 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, disposed = 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(restartable = false) {
|
|
80
|
-
if (dead) return disposed; 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
|
-
disposed = Promise.all([safe(() => loader?.whenDisposed?.()), safe(() => registry?.whenDisposed?.())]).then(() => { if (restartable === true && claimed) episodes.delete(host); });
|
|
85
|
-
return disposed;
|
|
86
|
-
}
|
|
87
|
-
function fail(reason) { if (dead) return; error = reason; dispose(); safe(() => onChange({ error, rows: [], effects: [], loading: false })); }
|
|
88
|
-
const unavailable = { ok: false, sync() {}, changedChat() {}, reply() {}, detachChat() {}, attachChat() { return null; }, attachTrial() { return null; }, stopTrial() {}, trialSnapshot: () => ({ rows: [] }), snapshot: () => snapshot(null), dispose };
|
|
89
|
-
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;
|
|
90
|
-
episodes.add(host); claimed = true; // Claim before invoking current/auth callbacks: one attempt per Window episode.
|
|
91
|
-
if (!alive()) { dispose(); return unavailable; }
|
|
92
|
-
try {
|
|
93
|
-
offAuth = auth.onSessionEnd(dispose);
|
|
94
|
-
if (!alive()) { dispose(); safe(() => offAuth?.()); return unavailable; }
|
|
95
|
-
host.addEventListener?.("pagehide", dispose);
|
|
96
|
-
} catch { fail("runtime_unavailable"); return unavailable; }
|
|
97
|
-
const make = (key, fn) => factories[key] ?? fn;
|
|
98
|
-
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; }
|
|
99
|
-
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; };
|
|
100
|
-
try {
|
|
101
|
-
writer = createEffectWriter(host, alive);
|
|
102
|
-
loader = make("loader", createEffectRuntimeLoader)({ window: host, ownerEpoch: identity.epoch, current: alive, onRevoke: () => fail("runtime_unavailable") });
|
|
103
|
-
if (!loader.ok || typeof loader.placementsSettled !== "function" || typeof loader.resourceSettled !== "function") throw Error("runtime_unavailable");
|
|
104
|
-
registry = make("registry", createEffectRegistry)({ window: host, origin: host.location?.origin, boot: host.document.body.dataset.effectBoot,
|
|
105
|
-
load: async (descriptor, signal) => {
|
|
106
|
-
const b = bindingFor(descriptor.key, descriptor.id);
|
|
107
|
-
if (!bindingAlive(b) || descriptor.id !== b.id || signal.aborted) return null;
|
|
108
|
-
if (b.kind === "trial") return trial.load(b, signal);
|
|
109
|
-
if (b.kind === "owner") return personal.load(b, signal);
|
|
110
|
-
const value = await loader.resourceSettled({ id: b.id, instance: b.source.name }, signal);
|
|
111
|
-
if (!bindingAlive(b) || signal.aborted) return null;
|
|
112
|
-
if (!value) { refuseSource(b.source); return null; }
|
|
113
|
-
return value;
|
|
114
|
-
},
|
|
115
|
-
safety: make("safety", createEffectSafety)({ storage: host.localStorage, crypto: host.crypto }),
|
|
116
|
-
stockSnapshot: ({ frameRoot }) => make("stock", stockEffectTrust)(host, { frameRoot, ownerCss: observe().ownerCss }),
|
|
117
|
-
onChange() { try { tell(); emit(); } catch { fail("runtime_unavailable"); } },
|
|
118
|
-
onMount(owner) {
|
|
119
|
-
const b = bindingFor(owner.key, owner.id); if (identity.scope !== "owner" || !bindingAlive(b) || b.id !== owner.id || !owner.current()) return;
|
|
120
|
-
const collector = make("collector", createEffectProfileCollector)({ key: owner.key, id: owner.id, device: owner.device, clock });
|
|
121
|
-
const began = safe(clock);
|
|
122
|
-
if (collector.ok && Number.isFinite(began) && began >= 0 && bindingAlive(b) && owner.current()) profiles.set(owner, { binding: b, collector, began, attempted: null }); else collector.dispose?.();
|
|
123
|
-
},
|
|
124
|
-
onRetire(owner) { profiles.get(owner)?.collector.dispose(); profiles.delete(owner); },
|
|
125
|
-
onProfile(raw, owner) {
|
|
126
|
-
const row = profiles.get(owner); if (!row || !bindingAlive(row.binding) || !owner.current()) return;
|
|
127
|
-
row.collector.add(raw); if (row.binding.kind === "trial") return; // Diagnostic samples stay transient; shared safety still runs in registry.
|
|
128
|
-
const now = clock();
|
|
129
|
-
if (!Number.isFinite(now) || now < row.began || now - row.began < 10000 && raw.verdict !== "paused" || row.attempted !== null && now - row.attempted < 10000) return;
|
|
130
|
-
const report = row.collector.report(); if (!report || !bindingAlive(row.binding) || !owner.current()) return;
|
|
131
|
-
row.attempted = now;
|
|
132
|
-
const query = row.binding.kind === "owner" ? "" : `instance=${encodeURIComponent(row.binding.source.name)}&`;
|
|
133
|
-
void writer.post(`/ui/effects/profile/${owner.id}?${query}ownerEpoch=${identity.epoch}`, report.profile);
|
|
134
|
-
}, clock,
|
|
135
|
-
});
|
|
136
|
-
if (!registry.ok) {
|
|
137
|
-
// Keep authenticated placement metadata available for explanations. This inert adapter cannot create,
|
|
138
|
-
// load, resume, send to, or authorize a frame; a failed paint subsystem is never permission.
|
|
139
|
-
playbackUnavailable = "unsupported_host";
|
|
140
|
-
safe(() => registry?.dispose?.());
|
|
141
|
-
registry = { ok: true, frameRoot: null, update() {}, snapshot: () => [], publish() {}, resume: () => false, dispose() {} };
|
|
142
|
-
}
|
|
143
|
-
if (identity.scope === "owner") trial = make("trialSource", createEffectTrialSource)({ window: host, current: () => {
|
|
144
|
-
if (!alive()) return false; const v = observe(), c = v.context;
|
|
145
|
-
return alive() && c.enabled === true && c.show === true && c.transportReady === true
|
|
146
|
-
&& c.safe === false && c.publicLink === false && c.reducedMotion === false && c.hidden === false;
|
|
147
|
-
}, ownerCurrent: (s, name) => sourceAlive(s) && s.name === name,
|
|
148
|
-
stock: () => registry.frameRoot ? make("stock", stockEffectTrust)(host, { frameRoot: registry.frameRoot, ownerCss: observe().ownerCss }) : null,
|
|
149
|
-
readResource: (spec, signal) => typeof loader.trialResourceSettled === "function" ? loader.trialResourceSettled(spec, signal) : Promise.resolve(null),
|
|
150
|
-
onChange() { revision++; stamp = null; sync(); tell(); },
|
|
151
|
-
});
|
|
152
|
-
if (identity.scope === "owner" && typeof resolveOwnerTarget === "function") {
|
|
153
|
-
if (typeof loader.ownerSettled !== "function") throw Error("runtime_unavailable");
|
|
154
|
-
personal = createOwnerEffectSource({ current: alive,
|
|
155
|
-
readMetadata: signal => loader.ownerSettled(signal), readResource: (spec, signal) => loader.resourceSettled(spec, signal), resolveTarget: resolveOwnerTarget,
|
|
156
|
-
stock: () => registry.frameRoot ? make("stock", stockEffectTrust)(host, { frameRoot: registry.frameRoot, ownerCss: observe().ownerCss }) : null,
|
|
157
|
-
onChange() { revision++; stamp = null; sync(); tell(); }, onUnavailable: (slot, id) => safe(() => onOwnerUnavailable(slot, id)),
|
|
158
|
-
});
|
|
159
|
-
if (!personal.ok) throw Error("runtime_unavailable");
|
|
160
|
-
}
|
|
161
|
-
} catch { fail("runtime_unavailable"); return unavailable; }
|
|
162
|
-
function descriptors(s) {
|
|
163
|
-
if (!sourceAlive(s) || s.error) return [];
|
|
164
|
-
return (s.metadata?.effects ?? []).filter(p => p.manifest.regions.includes("chat")).map((p, order) => {
|
|
165
|
-
let b = s.bindings.get(p.id); const identity = placementIdentity(p);
|
|
166
|
-
if (!b || b.identity !== identity) {
|
|
167
|
-
if (b) bindings.delete(b.key);
|
|
168
|
-
b = Object.freeze({ key: `chat:${s.serial}:${p.id}`, id: p.id, source: s, generation: s.generation, identity });
|
|
169
|
-
s.bindings.set(p.id, b); bindings.set(b.key, b);
|
|
170
|
-
}
|
|
171
|
-
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 };
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
function observation() {
|
|
175
|
-
const value = observe();
|
|
176
|
-
return { ...value, context: { ...value.context, openInstance: sourceAlive(source) ? source.name : null } };
|
|
177
|
-
}
|
|
178
|
-
function emit() {
|
|
179
|
-
if (!alive() || !registry?.ok) return;
|
|
180
|
-
const s = source, value = observation();
|
|
181
|
-
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); }
|
|
182
|
-
if (alive() && ["light", "dark"].includes(value.mode)) registry.publish("theme", { mode: value.mode });
|
|
183
|
-
}
|
|
184
|
-
function refuseSource(s) {
|
|
185
|
-
if (!sourceAlive(s)) return;
|
|
186
|
-
s.error = "resource_unavailable"; s.generation++;
|
|
187
|
-
for (const b of s.bindings.values()) bindings.delete(b.key); s.bindings.clear(); revision++; stamp = null; sync(); tell();
|
|
188
|
-
}
|
|
189
|
-
function queueSync() {
|
|
190
|
-
if (queued || dead) return; queued = true;
|
|
191
|
-
Promise.resolve().then(() => { queued = false; if (alive()) sync(); else dispose(); });
|
|
192
|
-
}
|
|
193
|
-
function sync() {
|
|
194
|
-
if (!alive()) { dispose(); return; } if (syncing) { queueSync(); return; }
|
|
195
|
-
syncing = true;
|
|
196
|
-
try {
|
|
197
|
-
const candidate = source;
|
|
198
|
-
if (candidate && !sourceAlive(candidate)) discard(candidate);
|
|
199
|
-
const before = revision, value = observation(), s = source;
|
|
200
|
-
if (!alive() || revision !== before || s !== source) { if (alive()) queueSync(); return; }
|
|
201
|
-
const entries = [...(personal?.descriptors() ?? []), ...(trial?.descriptors() ?? []), ...descriptors(s)];
|
|
202
|
-
if (!alive() || revision !== before || s !== source) { stamp = null; if (alive()) queueSync(); return; }
|
|
203
|
-
lastContext = { ...value.context };
|
|
204
|
-
const next = JSON.stringify([revision, value.context, entries.map(d => [d.key, d.paused])]);
|
|
205
|
-
if (stamp !== next) {
|
|
206
|
-
if (!alive() || revision !== before || s !== source) { stamp = null; if (alive()) queueSync(); return; }
|
|
207
|
-
stamp = next; registry.update(entries, value.context);
|
|
208
|
-
}
|
|
209
|
-
if (!alive() || revision !== before || s !== source) { stamp = null; if (alive()) queueSync(); return; }
|
|
210
|
-
emit();
|
|
211
|
-
const c = value.context;
|
|
212
|
-
// Metadata describes placements, not executable resources. Its existing session/epoch/current and
|
|
213
|
-
// shared settled-lane fences apply even when this viewer has declined playback. No code is fetched here.
|
|
214
|
-
if (!pending) {
|
|
215
|
-
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;
|
|
216
|
-
if (!alive() || revision !== before || s !== source) { if (alive()) queueSync(); return; }
|
|
217
|
-
if (ownerNeeds && (!chatNeeds || lastMetadata !== "owner")) void readPersonal();
|
|
218
|
-
else if (chatNeeds) void read(s);
|
|
219
|
-
}
|
|
220
|
-
tell();
|
|
221
|
-
} catch { fail("runtime_unavailable"); }
|
|
222
|
-
finally { syncing = false; }
|
|
223
|
-
}
|
|
224
|
-
async function readPersonal() {
|
|
225
|
-
if (!personal || pending || !personal.needsMetadata() || !alive()) return;
|
|
226
|
-
const op = { source: personal, controller: new host.AbortController() }; pending = op; lastMetadata = "owner";
|
|
227
|
-
try { await personal.read(op.controller.signal); }
|
|
228
|
-
catch { fail("runtime_unavailable"); }
|
|
229
|
-
finally { if (pending === op) { pending = null; if (alive()) sync(); } }
|
|
230
|
-
}
|
|
231
|
-
function changedOwner() {
|
|
232
|
-
if (!personal || !alive()) return;
|
|
233
|
-
if (pending?.source === personal) safe(() => pending.controller.abort());
|
|
234
|
-
personal.invalidate();
|
|
235
|
-
}
|
|
236
|
-
function ownerSnapshot() {
|
|
237
|
-
return { ...(personal?.snapshot() ?? { error: null, rev: -1, loading: false, slots: {} }),
|
|
238
|
-
rows: registry?.snapshot().flatMap(row => { const b = personal?.binding(row.key, row.id); return b ? [{ ...row, slot: b.slot }] : []; }) ?? [] };
|
|
239
|
-
}
|
|
240
|
-
async function cursorImageSettled(selection, signal) {
|
|
241
|
-
const own = personal?.cursorSelection();
|
|
242
|
-
if (!alive() || !own || !personal.cursorCurrent(selection) || signal?.aborted) return null;
|
|
243
|
-
let value; try { value = await loader.cursorImageSettled(own.id, signal); } catch { value = null; }
|
|
244
|
-
if (!alive() || !personal.cursorCurrent(own) || !personal.cursorCurrent(selection) || signal?.aborted) return null;
|
|
245
|
-
if (!value) { personal.refuseCursor(own); return null; }
|
|
246
|
-
return value;
|
|
247
|
-
}
|
|
248
|
-
async function read(s) {
|
|
249
|
-
if (!sourceAlive(s) || pending || s.error) return;
|
|
250
|
-
const op = { source: s, generation: s.generation, controller: new host.AbortController() }; pending = op; lastMetadata = "chat";
|
|
251
|
-
try {
|
|
252
|
-
const answer = await loader.placementsSettled(s.name, op.controller.signal);
|
|
253
|
-
if (!sourceAlive(s) || op.generation !== s.generation || pending !== op || op.controller.signal.aborted) return;
|
|
254
|
-
if (!answer) { refuseSource(s); return; }
|
|
255
|
-
s.metadata = answer;
|
|
256
|
-
for (const [id, identity] of s.paused) if (!answer.effects.some(p => p.id === id && placementIdentity(p) === identity)) s.paused.delete(id);
|
|
257
|
-
revision++; stamp = null; sync(); tell();
|
|
258
|
-
} catch { if (sourceAlive(s) && op.generation === s.generation) refuseSource(s); }
|
|
259
|
-
finally { if (pending === op) { pending = null; if (alive()) sync(); } }
|
|
260
|
-
}
|
|
261
|
-
function changedSource(s) {
|
|
262
|
-
if (!sourceAlive(s) || s.error) return;
|
|
263
|
-
s.generation++; s.metadata = null;
|
|
264
|
-
for (const b of s.bindings.values()) bindings.delete(b.key); s.bindings.clear();
|
|
265
|
-
if (pending?.source === s) safe(() => pending.controller.abort()); revision++; stamp = null; sync(); tell();
|
|
266
|
-
}
|
|
267
|
-
function publishReply(s, event) { if (host.document.body.dataset.webTransport !== "poll" && sourceAlive(s) && !s.error && event.instance === s.name) registry.publish("reply", {}, s.name); }
|
|
268
|
-
function attachChat(options) {
|
|
269
|
-
discard(source); const expected = revision; sync();
|
|
270
|
-
if (!alive() || revision !== expected || source !== null) return null;
|
|
271
|
-
const { name, element, current: ownCurrent, observe: ownObserve, onChange: ownChange = () => {} } = options ?? {};
|
|
272
|
-
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;
|
|
273
|
-
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() };
|
|
274
|
-
source = s;
|
|
275
|
-
s.reporter = createEffectWriter(host, () => sourceAlive(s)); s.reported = new Map(); s.reportClock = null;
|
|
276
|
-
s.reportTimer = host.setTimeout(() => reportPlayback(s), 10000);
|
|
277
|
-
revision++; stamp = null;
|
|
278
|
-
const placement = id => sourceAlive(s) && !s.error ? s.metadata?.effects.find(p => p.id === id) : null;
|
|
279
|
-
const changed = () => changedSource(s);
|
|
280
|
-
const api = { ok: true, sync,
|
|
281
|
-
changed,
|
|
282
|
-
reply(event) { publishReply(s, event); },
|
|
283
|
-
pause(id) { const p = placement(id); if (!p) return false; s.paused.set(id, placementIdentity(p)); revision++; sync(); return true; },
|
|
284
|
-
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); },
|
|
285
|
-
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; },
|
|
286
|
-
snapshot: () => sourceAlive(s) ? snapshot(s) : { error: null, rows: [], effects: [], loading: false },
|
|
287
|
-
dispose() { if (source !== s) return; discard(s); sync(); },
|
|
288
|
-
};
|
|
289
|
-
sync(); tell(); return sourceAlive(s) ? api : null;
|
|
290
|
-
}
|
|
291
|
-
return { ok: true, playbackAvailable: playbackUnavailable === null, sync, attachChat, changedOwner, ownerSnapshot,
|
|
292
|
-
attachTrial(options) { const s = source; return trial && !playbackUnavailable && sourceAlive(s) ? trial.attach(options, s) : null; },
|
|
293
|
-
stopTrial() { trial?.stop(); },
|
|
294
|
-
trialSnapshot() { return { rows: registry?.snapshot().filter(row => trial?.binding(row.key, row.id)) ?? [] }; },
|
|
295
|
-
cursorSelection: () => personal?.cursorSelection() ?? null, cursorImageSettled,
|
|
296
|
-
pauseOwner(slot) { return personal?.pause(slot) === true; },
|
|
297
|
-
resumeOwner(slot) { if (!personal?.unpause(slot)) return false; const b = personal.slotBinding(slot); return bindingAlive(b) && registry.resume(b.key); },
|
|
298
|
-
changedChat() { changedSource(source); }, reply(event) { publishReply(source, event); }, detachChat() { discard(source); sync(); }, snapshot: () => snapshot(source), dispose };
|
|
299
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { chatEffectIdentity } from "./effect-display-metadata.js";
|
|
2
|
-
/** #1629: one committed local Chat lease joins the reviewed host-only effect primitives. */
|
|
3
|
-
import { createEffectRuntimeLoader } from "./effect-runtime-loader.js";
|
|
4
|
-
import { createEffectRegistry } from "./effect-registry.js";
|
|
5
|
-
import { createEffectSafety } from "./effect-safety.js";
|
|
6
|
-
import { stockEffectTrust } from "./effect-stock-trust.js";
|
|
7
|
-
import { createEffectProfileCollector } from "./effect-profiles.js";
|
|
8
|
-
|
|
9
|
-
const writers = new WeakMap();
|
|
10
|
-
/** Physical write reservations outlive a canceled caller. Responses never supply runtime authority. */
|
|
11
|
-
export function createEffectWriter(host, current) {
|
|
12
|
-
let dead = false;
|
|
13
|
-
const own = new Set();
|
|
14
|
-
if (!writers.has(host)) writers.set(host, new Set());
|
|
15
|
-
const all = writers.get(host);
|
|
16
|
-
let previous = null;
|
|
17
|
-
function stop() { if (dead) return; dead = true; for (const op of own) op.cancel(); }
|
|
18
|
-
function time() {
|
|
19
|
-
let now; try { now = host.performance.now(); } catch { stop(); return null; }
|
|
20
|
-
if (!Number.isFinite(now) || now < 0 || previous !== null && now < previous) { stop(); return null; }
|
|
21
|
-
previous = now; return now;
|
|
22
|
-
}
|
|
23
|
-
return {
|
|
24
|
-
async post(path, body) {
|
|
25
|
-
if (dead || !current() || all.size >= 3) return false;
|
|
26
|
-
const began = time(); if (began === null) return false;
|
|
27
|
-
const text = JSON.stringify(body); if (new TextEncoder().encode(text).length > 16384) return false;
|
|
28
|
-
const controller = new host.AbortController(), op = { controller }; own.add(op); all.add(op);
|
|
29
|
-
let timer, cancel;
|
|
30
|
-
const aborted = new Promise(resolve => { cancel = () => { controller.abort(); resolve(false); }; }); op.cancel = cancel;
|
|
31
|
-
const work = Promise.resolve().then(async () => {
|
|
32
|
-
if (dead || !current() || controller.signal.aborted) return false;
|
|
33
|
-
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 });
|
|
34
|
-
// Settle the body before returning the physical reservation, even if the caller already timed out.
|
|
35
|
-
try { await r.body?.cancel(); } catch { /* no data is consumed */ }
|
|
36
|
-
const now = time();
|
|
37
|
-
return !dead && current() && !controller.signal.aborted && Number.isFinite(began) && began >= 0 && now !== null && now >= began && now - began < 10000 && r.ok && !r.redirected;
|
|
38
|
-
}).catch(() => false).finally(() => { host.clearTimeout(timer); controller.abort(); own.delete(op); all.delete(op); });
|
|
39
|
-
timer = host.setTimeout(cancel, 10000);
|
|
40
|
-
return Promise.race([work, aborted]);
|
|
41
|
-
},
|
|
42
|
-
dispose: stop,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Frozen, server-rendered session identity only. A header-token/public/View load has no identity. */
|
|
47
|
-
export { chatEffectIdentity } from "./effect-display-metadata.js";
|
|
48
|
-
/** Chat-only public/guest capability stays separate from personal preferences and management. */
|
|
49
|
-
export function effectPlaybackIdentity(host) {
|
|
50
|
-
try {
|
|
51
|
-
const b = host.document.body.dataset;
|
|
52
|
-
if (b.mode !== "full" || typeof b.effectEpoch !== "string" || !/^[a-f0-9]{16}$/.test(b.effectEpoch)) return null;
|
|
53
|
-
if (b.effectScope === "owner") {
|
|
54
|
-
const own = chatEffectIdentity(host);
|
|
55
|
-
return own?.epoch === b.effectEpoch ? Object.freeze({ epoch: b.effectEpoch, scope: "owner" }) : null;
|
|
56
|
-
}
|
|
57
|
-
if (b.effectScope === "chat" && (b.publicLink === "1" && b.webTransport === "poll" || b.tier === "guest")) return Object.freeze({ epoch: b.effectEpoch, scope: "chat" });
|
|
58
|
-
} catch { /* no session capability */ }
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
export function chatEffectState(state, name) {
|
|
62
|
-
const inst = state.instances?.find(i => i.name === name);
|
|
63
|
-
if (!inst) return null;
|
|
64
|
-
if (["stopped", "paused"].includes(inst.status)) return "stopped";
|
|
65
|
-
if (Object.hasOwn(state.awaiting ?? {}, name)) return "needs_you";
|
|
66
|
-
return ["working", "stuck"].includes(state.exec?.[name]) ? "working" : state.exec?.[name] === "idle" ? "idle" : null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** `observe` supplies closed host gates, never event/message content. No timers poll server state. */
|
|
70
|
-
export function createChatEffectRuntime({ window: host, name, element, current, observe, onChange = () => {}, factories = {} }) {
|
|
71
|
-
let dead = false, error = null, loader = null, registry = null, writer = null, active = null, queued = false, generation = 0, metadata = null, ctx = null, lastContext = null;
|
|
72
|
-
const paused = new Map(), owners = new Map();
|
|
73
|
-
const placementIdentity = p => JSON.stringify([p.by, p.manifest.layer, p.params]);
|
|
74
|
-
const identity = chatEffectIdentity(host), auth = host.AgendAuth;
|
|
75
|
-
const alive = () => !dead && current() === true && element.isConnected === true && !auth.sessionEnded();
|
|
76
|
-
const tell = () => { if (!dead && current()) onChange({ error, rows: registry?.snapshot() ?? [], effects: metadata?.effects ?? [], loading: metadata === null && !error }); };
|
|
77
|
-
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(); };
|
|
78
|
-
function dispose() {
|
|
79
|
-
if (dead) return; dead = true; generation++; active?.controller.abort(); registry?.dispose?.(); loader?.dispose?.(); writer?.dispose(); offAuth?.();
|
|
80
|
-
for (const value of owners.values()) value.collector.dispose(); owners.clear();
|
|
81
|
-
}
|
|
82
|
-
let offAuth = null;
|
|
83
|
-
if (!identity || typeof auth?.onSessionEnd !== "function" || typeof auth?.sessionEnded !== "function" || typeof name !== "string" || name.length < 1 || name.length > 128 || !current()) return { ok: false, dispose };
|
|
84
|
-
offAuth = auth.onSessionEnd(dispose); // May synchronously replay an already-ended episode.
|
|
85
|
-
if (dead || !alive()) { dispose(); return { ok: false, dispose }; }
|
|
86
|
-
const clock = () => host.performance.now();
|
|
87
|
-
try {
|
|
88
|
-
const make = (key, fn) => factories[key] ?? fn;
|
|
89
|
-
writer = createEffectWriter(host, alive);
|
|
90
|
-
loader = make("loader", createEffectRuntimeLoader)({ window: host, ownerEpoch: identity.epoch, current: alive });
|
|
91
|
-
if (!loader.ok) throw Error("runtime_unavailable");
|
|
92
|
-
registry = make("registry", createEffectRegistry)({ window: host, origin: host.location?.origin, boot: host.document.body.dataset.effectBoot,
|
|
93
|
-
load: async (d, signal) => {
|
|
94
|
-
const mine = generation, value = await loader.resource({ id: d.id, instance: name }, signal);
|
|
95
|
-
if (!alive() || mine !== generation || signal.aborted) return null;
|
|
96
|
-
if (!value) { fail("resource_unavailable"); return null; }
|
|
97
|
-
return value;
|
|
98
|
-
},
|
|
99
|
-
safety: make("safety", createEffectSafety)({ storage: host.localStorage, crypto: host.crypto }),
|
|
100
|
-
stockSnapshot: ({ frameRoot }) => make("stock", stockEffectTrust)(host, { frameRoot, ownerCss: observe().ownerCss }),
|
|
101
|
-
onChange() { tell(); emit(); },
|
|
102
|
-
onMount(owner) {
|
|
103
|
-
if (!alive() || !owner.current()) return;
|
|
104
|
-
const collector = make("collector", createEffectProfileCollector)({ key: owner.key, id: owner.id, device: owner.device, clock });
|
|
105
|
-
if (collector.ok) owners.set(owner, { collector, began: clock(), attempted: null });
|
|
106
|
-
},
|
|
107
|
-
onRetire(owner) { const row = owners.get(owner); row?.collector.dispose(); owners.delete(owner); },
|
|
108
|
-
onProfile(raw, owner) {
|
|
109
|
-
const row = owners.get(owner); if (!row || !alive() || !owner.current()) return;
|
|
110
|
-
row.collector.add(raw);
|
|
111
|
-
const now = clock();
|
|
112
|
-
if (now - row.began < 10000 && raw.verdict !== "paused" || row.attempted !== null && now - row.attempted < 10000) return;
|
|
113
|
-
const report = row.collector.report(); if (!report || !owner.current()) return;
|
|
114
|
-
row.attempted = now;
|
|
115
|
-
void writer.post(`/ui/effects/profile/${owner.id}?instance=${encodeURIComponent(name)}&ownerEpoch=${identity.epoch}`, report.profile);
|
|
116
|
-
}, clock,
|
|
117
|
-
});
|
|
118
|
-
if (!registry.ok) throw Error("runtime_unavailable");
|
|
119
|
-
} catch { fail("runtime_unavailable"); }
|
|
120
|
-
function descriptors() {
|
|
121
|
-
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,
|
|
122
|
-
order, paused: paused.get(p.id) === placementIdentity(p), visible: true, instance: name, params: p.params, element }));
|
|
123
|
-
}
|
|
124
|
-
function emit() {
|
|
125
|
-
if (!alive() || error || !registry?.ok) return;
|
|
126
|
-
const value = observe();
|
|
127
|
-
if (["idle", "working", "needs_you", "stopped"].includes(value.state)) registry.publish("state", { state: value.state }, name);
|
|
128
|
-
if (["light", "dark"].includes(value.mode)) registry.publish("theme", { mode: value.mode });
|
|
129
|
-
}
|
|
130
|
-
function sync() {
|
|
131
|
-
if (!alive()) { dispose(); return; } if (error) return;
|
|
132
|
-
const value = observe(); ctx = value.context;
|
|
133
|
-
const stamp = JSON.stringify(ctx);
|
|
134
|
-
if (stamp !== lastContext) { lastContext = stamp; registry.update(descriptors(), ctx); }
|
|
135
|
-
emit();
|
|
136
|
-
if (!metadata && !active && ctx.enabled && ctx.show && ctx.transportReady && !ctx.safe && !ctx.publicLink && !ctx.reducedMotion && !ctx.hidden) void read();
|
|
137
|
-
}
|
|
138
|
-
async function read() {
|
|
139
|
-
if (!alive() || error || active) return;
|
|
140
|
-
const op = { controller: new host.AbortController(), generation }; active = op;
|
|
141
|
-
try {
|
|
142
|
-
const answer = await loader.placements(name, op.controller.signal);
|
|
143
|
-
if (!alive() || active !== op) return;
|
|
144
|
-
if (op.generation !== generation) return;
|
|
145
|
-
if (!answer) { fail("resource_unavailable"); return; }
|
|
146
|
-
metadata = answer;
|
|
147
|
-
for (const [id, stamp] of paused) if (!answer.effects.some(p => p.id === id && placementIdentity(p) === stamp)) paused.delete(id);
|
|
148
|
-
lastContext = null; sync(); tell();
|
|
149
|
-
} finally {
|
|
150
|
-
if (active === op) active = null;
|
|
151
|
-
if (queued && alive() && !error) { queued = false; sync(); }
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return { ok: !error && !dead, sync,
|
|
155
|
-
changed() { if (!alive() || error) return; generation++; metadata = null; registry.update([], observe().context); lastContext = null; if (active) queued = true; else sync(); tell(); },
|
|
156
|
-
reply(event) { if (alive() && !error && event.instance === name) registry.publish("reply", {}, name); },
|
|
157
|
-
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; },
|
|
158
|
-
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}`); },
|
|
159
|
-
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; },
|
|
160
|
-
snapshot: () => ({ error, rows: registry?.snapshot() ?? [], effects: metadata?.effects ?? [], loading: metadata === null && !error }), dispose,
|
|
161
|
-
};
|
|
162
|
-
}
|