@songsid/agend 2.2.0 → 2.3.0-beta.1
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.js +248 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +2 -0
- package/dist/agent-endpoint.js +25 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-file-read.d.ts +30 -0
- package/dist/agent-file-read.js +129 -0
- package/dist/agent-file-read.js.map +1 -0
- package/dist/agent-whats-new.js +13 -0
- 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.js +17 -0
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/antigravity.js +3 -3
- package/dist/backend/antigravity.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/channel/adapters/discord.d.ts +2 -0
- package/dist/channel/adapters/discord.js +17 -2
- 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 +34 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/types.d.ts +15 -0
- package/dist/config-validator.js +17 -1
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.js +244 -20
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-context.d.ts +5 -0
- package/dist/fleet-manager.d.ts +89 -0
- package/dist/fleet-manager.js +401 -25
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +258 -0
- package/dist/instance-avatar.js +3 -0
- package/dist/instance-avatar.js.map +1 -1
- package/dist/instance-config-impact.js +3 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.js +9 -4
- package/dist/instance-lifecycle.js.map +1 -1
- 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 +5 -0
- package/dist/outbound-handlers.js +30 -9
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +64 -0
- package/dist/outbound-schemas.js +50 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +14 -0
- package/dist/public-web-gateway.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/scheduler/scheduler.d.ts +58 -1
- package/dist/scheduler/scheduler.js +176 -10
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/settings-api.js +74 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +8 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +16 -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/tool-permissions.js +50 -0
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +10 -6
- package/dist/topic-commands.js +21 -17
- 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/types.d.ts +13 -0
- package/dist/ui/app-effects.js +187 -0
- package/dist/ui/chat-effect-posters.js +42 -0
- package/dist/ui/chat-effects.js +72 -0
- package/dist/ui/chat-live-replies.js +58 -0
- package/dist/ui/chat-render.js +4 -4
- package/dist/ui/chat-store.js +9 -3
- package/dist/ui/chat-strings.js +15 -0
- package/dist/ui/chat-thread.js +47 -21
- package/dist/ui/chat-tour.js +1 -1
- package/dist/ui/effect-trial-controller.js +119 -0
- package/dist/ui/effect-trial-offer.js +31 -0
- package/dist/ui/effect-trial-panel.js +37 -0
- package/dist/ui/image-lightbox.js +1 -1
- package/dist/ui/panel-chat.js +85 -17
- package/dist/ui/panel-details.js +18 -5
- package/dist/ui/panel-fleet.js +3 -3
- package/dist/ui/panel-log.js +6 -6
- package/dist/ui/panel-needs.js +3 -3
- package/dist/ui/panel-settings.js +8 -5
- package/dist/ui/preview-panel.js +25 -9
- package/dist/ui/settings-appearance.js +325 -0
- package/dist/ui/settings-effects.js +201 -0
- package/dist/ui/settings-mods.js +257 -0
- package/dist/ui/settings-owner-effects.js +80 -0
- package/dist/ui/settings-strings.js +245 -0
- package/dist/ui/shared/agend-auth.js +27 -3
- package/dist/ui/shared/agent-widget.js +248 -0
- package/dist/ui/shared/app-i18n.js +2 -0
- package/dist/ui/shared/app-prefs.js +30 -10
- package/dist/ui/shared/app-route.js +23 -1
- package/dist/ui/shared/app-session.js +1 -1
- package/dist/ui/shared/app-shell.js +29 -16
- package/dist/ui/shared/app-store.js +2 -0
- package/dist/ui/shared/app-stream.js +12 -7
- package/dist/ui/shared/app-themes.js +188 -0
- package/dist/ui/shared/app.css +214 -1
- package/dist/ui/shared/app.js +6 -0
- package/dist/ui/shared/card-strings.js +28 -0
- package/dist/ui/shared/effect-app-runtime.js +248 -0
- package/dist/ui/shared/effect-baseline.js +64 -0
- package/dist/ui/shared/effect-chat-runtime.js +154 -0
- package/dist/ui/shared/effect-cursor.js +125 -0
- package/dist/ui/shared/effect-frame.js +127 -0
- package/dist/ui/shared/effect-geometry.js +122 -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 +115 -0
- package/dist/ui/shared/effect-policy.js +137 -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 +162 -0
- package/dist/ui/shared/effect-profiles.js +65 -0
- package/dist/ui/shared/effect-registry.js +496 -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 +34 -0
- package/dist/ui/shared/effect-stock-trust.js +165 -0
- package/dist/ui/shared/effect-trial-source.js +103 -0
- package/dist/ui/shared/effect-trial-stage.js +147 -0
- package/dist/ui/shared/header-tools.js +1 -1
- package/dist/ui/shared/identity-card.js +202 -0
- package/dist/ui/shared/identity-color.js +261 -0
- package/dist/ui/shared/instance-nav.js +22 -14
- package/dist/ui/shared/owner-css.js +221 -0
- package/dist/ui/shared/owner-effect-prefs.js +117 -0
- package/dist/ui/shared/panel-view.js +81 -8
- package/dist/ui/shared/signin.js +2 -0
- package/dist/ui/shared/ui-dialog.js +1 -1
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/shared/ui-menu.js +1 -1
- package/dist/ui/shared/ui-toast.js +1 -1
- package/dist/ui/shared/view-strings.js +30 -0
- package/dist/ui/signin.html +1 -0
- package/dist/view-api.d.ts +29 -0
- package/dist/view-api.js +188 -8
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +42 -0
- package/dist/web-api.js +896 -36
- 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 +66 -0
- package/dist/web-appearance-policy.js +94 -0
- package/dist/web-appearance-policy.js.map +1 -0
- package/dist/web-appearance-tools.d.ts +50 -0
- package/dist/web-appearance-tools.js +105 -0
- package/dist/web-appearance-tools.js.map +1 -0
- package/dist/web-appearance-view.d.ts +99 -0
- package/dist/web-appearance-view.js +163 -0
- package/dist/web-appearance-view.js.map +1 -0
- package/dist/web-appearance.d.ts +79 -0
- package/dist/web-appearance.js +298 -0
- package/dist/web-appearance.js.map +1 -0
- package/dist/web-asset-parts.d.ts +98 -0
- package/dist/web-asset-parts.js +133 -0
- package/dist/web-asset-parts.js.map +1 -0
- package/dist/web-auth.js +17 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-card.d.ts +96 -0
- package/dist/web-card.js +232 -0
- package/dist/web-card.js.map +1 -0
- package/dist/web-chat-history.d.ts +13 -0
- package/dist/web-chat-history.js +23 -2
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-effect-bundle.d.ts +28 -0
- package/dist/web-effect-bundle.js +126 -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-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 +86 -0
- package/dist/web-effect-service.js +312 -0
- package/dist/web-effect-service.js.map +1 -0
- package/dist/web-effect-tools.d.ts +47 -0
- package/dist/web-effect-tools.js +126 -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 +29 -0
- package/dist/web-effect-upload.js +190 -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 +89 -0
- package/dist/web-effect.js +132 -0
- package/dist/web-effect.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 +25 -0
- package/dist/web-image-meta.js +200 -0
- package/dist/web-image-meta.js.map +1 -0
- package/dist/web-mod-agent.d.ts +50 -0
- package/dist/web-mod-agent.js +203 -0
- package/dist/web-mod-agent.js.map +1 -0
- package/dist/web-mod-bundle.d.ts +144 -0
- package/dist/web-mod-bundle.js +464 -0
- package/dist/web-mod-bundle.js.map +1 -0
- package/dist/web-mod-export.d.ts +59 -0
- package/dist/web-mod-export.js +79 -0
- package/dist/web-mod-export.js.map +1 -0
- package/dist/web-mod-store.d.ts +283 -0
- package/dist/web-mod-store.js +624 -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 +180 -0
- package/dist/web-owner-css-store.js +384 -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 +48 -0
- package/dist/web-prefs.js +209 -33
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +9 -1
- package/dist/web-preview.js +24 -7
- package/dist/web-preview.js.map +1 -1
- package/dist/web-settings-patch.d.ts +18 -0
- package/dist/web-settings-patch.js +51 -0
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-shell-routes.d.ts +1 -1
- package/dist/web-shell-routes.js +1 -1
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/web-theme-store.d.ts +218 -0
- package/dist/web-theme-store.js +516 -0
- package/dist/web-theme-store.js.map +1 -0
- package/dist/web-theme.d.ts +121 -0
- package/dist/web-theme.js +407 -0
- package/dist/web-theme.js.map +1 -0
- package/dist/web-widget.d.ts +72 -0
- package/dist/web-widget.js +245 -0
- package/dist/web-widget.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** #1629: bounded host-window quantiles; not a frame message handler or admission verdict. */
|
|
2
|
+
const plain = o => !!o && (Object.getPrototypeOf(o) === Object.prototype || Object.getPrototypeOf(o) === null);
|
|
3
|
+
const finite = x => typeof x === "number" && Number.isFinite(x);
|
|
4
|
+
const bounded = (x, max) => finite(x) && x >= 0 && x <= max;
|
|
5
|
+
const reasons = [null, "host_budget", "clock_unavailable", "gates_unknown", "hidden", "reduced_motion", "save_data", "viewer_off"];
|
|
6
|
+
const keys = ["key", "id", "device", "verdict", "reason", "remove", "forward", "quality", "pointerHz", "badWindows", "loadMs", "metrics"];
|
|
7
|
+
const metricKeys = ["at", "hostFps", "hostLongTaskMsPerSecond", "attributedLongTaskMsPerSecond", "attribution", "fpsFloor", "windowMs"];
|
|
8
|
+
const exact = (o, allowed) => plain(o) && Object.keys(o).sort().join(",") === [...allowed].sort().join(",");
|
|
9
|
+
const quantile = (values, q) => { if (!values.length) return null; const a = [...values].sort((x, y) => x - y); return a[Math.max(0, Math.ceil(q * a.length) - 1)]; };
|
|
10
|
+
|
|
11
|
+
/** A new collector belongs to one exact registry key/id/class mount. It never reads message content or child data. */
|
|
12
|
+
export function createEffectProfileCollector(options = {}) {
|
|
13
|
+
let key, id, device, clock;
|
|
14
|
+
try { if (!exact(options, ["key", "id", "device", "clock"])) return { ok: false }; ({ key, id, device, clock } = options); } catch { return { ok: false }; }
|
|
15
|
+
if (typeof key !== "string" || key.length < 1 || key.length > 256 || typeof id !== "string" || !/^[a-f0-9]{32}$/.test(id) || !["standard", "constrained"].includes(device) || typeof clock !== "function") return { ok: false };
|
|
16
|
+
let dead = false, previous = null, lastSample = null, floor = null, last = null;
|
|
17
|
+
const samples = [];
|
|
18
|
+
const time = () => {
|
|
19
|
+
let n; try { n = clock(); } catch { dead = true; return null; }
|
|
20
|
+
if (!finite(n) || n < 0 || previous !== null && n < previous) { dead = true; return null; }
|
|
21
|
+
previous = n; return n;
|
|
22
|
+
};
|
|
23
|
+
if (time() === null) return { ok: false };
|
|
24
|
+
return { ok: true,
|
|
25
|
+
add(raw) {
|
|
26
|
+
if (dead) return false;
|
|
27
|
+
try {
|
|
28
|
+
// Reject unknown keys without reading their values or accessors.
|
|
29
|
+
if (!exact(raw, keys) || raw.key !== key || raw.id !== id || raw.device !== device || !["ok", "degraded", "paused"].includes(raw.verdict) || !reasons.includes(raw.reason)
|
|
30
|
+
|| typeof raw.remove !== "boolean" || typeof raw.forward !== "boolean" || raw.remove !== (raw.verdict === "paused") || raw.remove && raw.forward
|
|
31
|
+
|| raw.quality !== (raw.verdict === "ok" ? "normal" : "low") || raw.pointerHz !== (raw.verdict === "ok" ? 60 : 30)
|
|
32
|
+
|| !Number.isInteger(raw.badWindows) || raw.badWindows < 0 || raw.badWindows > 3 || raw.loadMs !== null && !bounded(raw.loadMs, 600000)) { dead = true; return false; }
|
|
33
|
+
const n = time(); if (n === null) return false;
|
|
34
|
+
// Warmup is not an observed window and never turns missing measurements into zero cost.
|
|
35
|
+
if (raw.metrics === null) return false;
|
|
36
|
+
const m = raw.metrics;
|
|
37
|
+
if (!exact(m, metricKeys) || !finite(m.at) || m.at < 0 || m.at > n || !bounded(m.hostFps, 1000) || !finite(m.fpsFloor) || m.fpsFloor <= 0 || m.fpsFloor > 1000 || m.windowMs !== 2000
|
|
38
|
+
|| m.hostLongTaskMsPerSecond !== null && !bounded(m.hostLongTaskMsPerSecond, 256000)
|
|
39
|
+
|| m.attributedLongTaskMsPerSecond !== null && (!bounded(m.attributedLongTaskMsPerSecond, 256000) || m.hostLongTaskMsPerSecond === null || m.attributedLongTaskMsPerSecond > m.hostLongTaskMsPerSecond)
|
|
40
|
+
|| m.attribution !== (m.attributedLongTaskMsPerSecond === null ? "unknown" : "observed") || floor !== null && m.fpsFloor !== floor) { dead = true; return false; }
|
|
41
|
+
// Only poll() creates a window. rAF/gates/load/stop snapshots retain its immutable host timestamp.
|
|
42
|
+
// A paused duplicate may describe the last verdict, but cannot renew the observation or add a sample.
|
|
43
|
+
if (lastSample !== null && m.at === lastSample && last) last = { verdict: raw.verdict, reason: raw.reason, loadMs: raw.loadMs };
|
|
44
|
+
if (n - m.at > 5000 || lastSample !== null && m.at - lastSample < 1000) return false;
|
|
45
|
+
floor = m.fpsFloor; lastSample = m.at;
|
|
46
|
+
samples.push({ fps: m.hostFps, task: m.hostLongTaskMsPerSecond, own: m.attributedLongTaskMsPerSecond });
|
|
47
|
+
if (samples.length > 120) samples.shift();
|
|
48
|
+
last = { verdict: raw.verdict, reason: raw.reason, loadMs: raw.loadMs };
|
|
49
|
+
return true;
|
|
50
|
+
} catch { dead = true; return false; }
|
|
51
|
+
},
|
|
52
|
+
report(options = {}) {
|
|
53
|
+
let off;
|
|
54
|
+
try { if (!plain(options) || Object.keys(options).some(k => k !== "off")) return null; off = options.off ?? false; } catch { dead = true; return null; }
|
|
55
|
+
if (dead || typeof off !== "boolean" || lastSample === null || !last) return null;
|
|
56
|
+
const n = time(); if (n === null || n - lastSample > 5000) return null;
|
|
57
|
+
const host = samples.filter(s => s.task !== null).map(s => s.task), own = samples.filter(s => s.own !== null).map(s => s.own), fps = samples.map(s => s.fps);
|
|
58
|
+
return { id, profile: { v: 1, device, verdict: off ? "off" : last.verdict, reason: last.reason, samples: samples.length,
|
|
59
|
+
fps_p50: quantile(fps, 0.5), fps_p10: quantile(fps, 0.1), fps_floor: floor,
|
|
60
|
+
long_task_p95: quantile(host, 0.95), long_task_samples: host.length,
|
|
61
|
+
attributed_long_task_p95: quantile(own, 0.95), attributed_samples: own.length, attribution: own.length ? "observed" : "unknown", load_ms: last.loadMs } };
|
|
62
|
+
},
|
|
63
|
+
dispose() { dead = true; samples.length = 0; last = null; },
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
/** #1629 B2: one host-owned Canvas Worker pool. Product adapters are deliberately separate. */
|
|
2
|
+
import { allocateEffectFrames, effectEvent } from "./effect-policy.js";
|
|
3
|
+
import { captureEffectBaseline } from "./effect-baseline.js";
|
|
4
|
+
import { createEffectFrame } from "./effect-frame.js";
|
|
5
|
+
import { createEffectGeometry, hostEffectRect } from "./effect-geometry.js";
|
|
6
|
+
import { effectStockSnapshot, effectStockAllows } from "./effect-stock-snapshot.js";
|
|
7
|
+
import { effectTrialStage, effectTrialStageIdentity, watchEffectTrialStage } from "./effect-trial-stage.js";
|
|
8
|
+
import { EffectBudget } from "./effect-profiler.js";
|
|
9
|
+
|
|
10
|
+
const registries = new WeakMap();
|
|
11
|
+
const plain = o => !!o && (Object.getPrototypeOf(o) === Object.prototype || Object.getPrototypeOf(o) === null);
|
|
12
|
+
const finite = v => typeof v === "number" && Number.isFinite(v);
|
|
13
|
+
const FLAGS = ["safe", "publicLink", "reducedMotion", "saveData", "hidden", "enabled", "show", "previewReady", "optIn", "constrained", "modal"];
|
|
14
|
+
const TYPES = ["state", "reply", "theme"];
|
|
15
|
+
function unsafeSnapshotDescriptor(d) {
|
|
16
|
+
if (!plain(d) || !plain(d.params ?? {})) return null;
|
|
17
|
+
const params = Object.create(null), keys = Object.keys(d.params ?? {}).sort();
|
|
18
|
+
if (keys.length > 16) return null;
|
|
19
|
+
for (const k of keys) {
|
|
20
|
+
const v = d.params[k];
|
|
21
|
+
if (!/^[a-z][a-z0-9_]{0,31}$/.test(k) || !(typeof v === "boolean" || finite(v) || typeof v === "string" && v.length <= 128)) return null;
|
|
22
|
+
params[k] = v;
|
|
23
|
+
}
|
|
24
|
+
const trialIdentity = d.trialStage == null ? null : effectTrialStageIdentity(d.trialStage);
|
|
25
|
+
if (d.trialStage != null && (!trialIdentity || d.region !== "chat" || trialIdentity.key !== d.key || trialIdentity.id !== d.id || trialIdentity.instance !== d.instance || trialIdentity.generation !== d.generation)) return null;
|
|
26
|
+
const out = { trialStage: d.trialStage ?? null, generation: trialIdentity?.generation ?? null, key: d.key, id: d.id, region: d.region, layer: d.layer, by: !trialIdentity && d.by === "human" ? "human" : "agent", order: d.order,
|
|
27
|
+
paused: d.paused, visible: d.visible, instance: d.region === "chat" ? d.instance : null, params, element: d.element };
|
|
28
|
+
const identity = [out.id, out.region, out.layer, out.by, out.instance, params];
|
|
29
|
+
if (trialIdentity) identity.push([trialIdentity.serial, trialIdentity.generation]);
|
|
30
|
+
out.identity = JSON.stringify(identity);
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function snapshotDescriptor(d) { try { return unsafeSnapshotDescriptor(d); } catch { return null; } }
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* `load` is an authenticated, bounded host loader, never a Worker callback. It returns {id,code,params,assets,wants}.
|
|
38
|
+
* Safety is mandatory and synchronous: allowed(id,device), begin({key,id,device}), finish(token,reason), pause(record), resume(id,device). Pause persists a soft latch and history; human resume never clears sticky off.
|
|
39
|
+
* stockSnapshot({frameRoot}) supplies current closed stock paint evidence synchronously; it must not reenter a mutation API.
|
|
40
|
+
* Unknown receipts, roots, trust coverage or gates mount nothing. No storage, network, content or product imports here.
|
|
41
|
+
*/
|
|
42
|
+
export function createEffectRegistry({ window: host, origin, boot, load, safety, stockSnapshot, onChange = () => {}, onProfile = () => {}, onMount = () => {}, onRetire = () => {}, clock = () => host.performance.now() } = {}) {
|
|
43
|
+
try { if (!host?.document || !host.document.body || registries.has(host) || typeof load !== "function" || typeof stockSnapshot !== "function" || typeof onChange !== "function" || typeof onProfile !== "function" || typeof onMount !== "function" || typeof onRetire !== "function" || typeof clock !== "function" || !safety || ["allowed", "begin", "finish", "pause", "resume"].some(k => typeof safety[k] !== "function") || ["requestAnimationFrame", "cancelAnimationFrame", "setTimeout", "clearTimeout", "MutationObserver", "ResizeObserver", "AbortController"].some(k => typeof host[k] !== "function")) return { ok: false, reason: "registry_unsupported" }; } catch { return { ok: false, reason: "registry_unsupported" }; }
|
|
44
|
+
let dead = false, blocked = null, revision = 0, last = null, lastView = null, frameSerial = 0, context = null, descriptors = [], calibration = null, baseline = null, baselineDevice = null, serial = 0, raf = null, timer = null;
|
|
45
|
+
const slots = new Map(), loads = new Set(), paused = new Map(), offs = [], observed = new Set(), failures = new Map(), results = new Map();
|
|
46
|
+
let readingStock = false;
|
|
47
|
+
let root, geometry, observer, resize, longTasks = null, longTasksSupported = false;
|
|
48
|
+
const time = () => {
|
|
49
|
+
let n; try { n = clock(); } catch { return null; }
|
|
50
|
+
if (!finite(n) || n < 0 || last !== null && n < last) return null;
|
|
51
|
+
last = n; return n;
|
|
52
|
+
};
|
|
53
|
+
const nativeClock = () => { const n = time(); if (n === null) throw Error("clock unavailable"); return n; };
|
|
54
|
+
const device = () => context?.constrained === true ? "constrained" : "standard";
|
|
55
|
+
const dpr = device => Math.min(device === "constrained" ? 1.5 : 3, Math.max(0.5, host.devicePixelRatio || 1));
|
|
56
|
+
// The provider is synchronous and observational. Reentrant mutation revokes this pool, rather than
|
|
57
|
+
// allowing an old admission to act on newer descriptors or scheduling unbounded callback retries.
|
|
58
|
+
const readingStages = new Set();
|
|
59
|
+
// A local receipt may synchronously invalidate and notify its owner. Fence its result by revision;
|
|
60
|
+
// recursion on the same receipt is unknown, while an independently issued replacement may proceed.
|
|
61
|
+
const localStage = (d, proof) => {
|
|
62
|
+
if (!d?.trialStage || dead || blocked || readingStages.has(d.trialStage)) return null;
|
|
63
|
+
const before = revision; let stage = null; readingStages.add(d.trialStage);
|
|
64
|
+
try { stage = effectTrialStage(d.trialStage, d, proof); } catch { /* unknown is never permission */ }
|
|
65
|
+
finally { readingStages.delete(d.trialStage); }
|
|
66
|
+
return !dead && !blocked && revision === before && stage?.root === d.element ? stage : null;
|
|
67
|
+
};
|
|
68
|
+
const permits = (d, proof) => d.trialStage ? !!localStage(d, proof) : effectStockAllows(proof, d);
|
|
69
|
+
const coverage = (d = null) => {
|
|
70
|
+
if (dead || blocked || readingStock) return null;
|
|
71
|
+
const before = revision; let proof = null; readingStock = true;
|
|
72
|
+
try { proof = effectStockSnapshot(stockSnapshot(Object.freeze({ frameRoot: root }))); }
|
|
73
|
+
catch { /* unknown is never permission */ }
|
|
74
|
+
finally { readingStock = false; }
|
|
75
|
+
if (dead || blocked || before !== revision) return null;
|
|
76
|
+
return d === null || permits(d, proof) ? proof : null;
|
|
77
|
+
};
|
|
78
|
+
const position = (d, proof) => d.trialStage ? localStage(d, proof)?.rect ?? null : hostEffectRect(d.element);
|
|
79
|
+
const syncGeometry = (s, proof) => {
|
|
80
|
+
const before = revision, rect = position(s.d, proof);
|
|
81
|
+
if (!rect || !current(s) || before !== revision) return null;
|
|
82
|
+
const ok = s.d.trialStage ? s.geometry.sync(proof) : s.geometry.sync(rect, proof);
|
|
83
|
+
return ok && current(s) && before === revision ? rect : null;
|
|
84
|
+
};
|
|
85
|
+
const hideGeometry = () => { geometry?.hide(); for (const s of slots.values()) if (s.d.trialStage) s.geometry?.hide(); };
|
|
86
|
+
function reentrantStock() {
|
|
87
|
+
if (!readingStock) return false;
|
|
88
|
+
if (blocked) return true;
|
|
89
|
+
revision++; blocked = "stock_reentrant"; hideGeometry(); stopAll(blocked); tell(); return true;
|
|
90
|
+
}
|
|
91
|
+
const current = s => !dead && !blocked && slots.get(s.d.key) === s && s.live;
|
|
92
|
+
const tell = () => { if (dead) return; const state = view(), stamp = JSON.stringify(state); if (stamp === lastView) return; lastView = stamp; try { onChange(state); } catch { /* a display callback cannot admit code */ } };
|
|
93
|
+
const receipt = (fn, ...args) => { try { return Reflect.apply(fn, safety, args); } catch { return null; } };
|
|
94
|
+
const clearMarker = (s, why) => {
|
|
95
|
+
if (!s.token) return true;
|
|
96
|
+
const token = s.token; s.token = null;
|
|
97
|
+
if (receipt(safety.finish, token, why) === true) return true;
|
|
98
|
+
blocked = "safety_unknown"; return false;
|
|
99
|
+
};
|
|
100
|
+
function retire(s, why, count = false) {
|
|
101
|
+
if (!s.live) return;
|
|
102
|
+
s.live = false;
|
|
103
|
+
if (slots.get(s.d.key) === s) slots.delete(s.d.key);
|
|
104
|
+
if (s.request) { s.request.controller.abort(); if (s.request.timer !== null) host.clearTimeout(s.request.timer); }
|
|
105
|
+
if (!["disposed", "superseded", "hidden", "human_resume", "calibrating", "geometry_changed"].includes(why)) failures.set(s.d.key, { identity: signature(s.d), reason: why });
|
|
106
|
+
s.stageOff?.(); s.stageOff = null;
|
|
107
|
+
s.frame?.remove(why); s.geometry?.remove();
|
|
108
|
+
if (s.loadOff) s.loadOff();
|
|
109
|
+
if (count) {
|
|
110
|
+
paused.set(s.d.key, signature(s.d));
|
|
111
|
+
if (receipt(safety.pause, { id: s.d.id, device: s.device, reason: why, metrics: s.budget?.snapshot().metrics ?? null }) !== true) blocked = "safety_unknown";
|
|
112
|
+
}
|
|
113
|
+
clearMarker(s, why);
|
|
114
|
+
if (s.owner) { try { onRetire(s.owner, why); } catch { /* an observation cannot prevent physical cleanup */ } }
|
|
115
|
+
if (blocked) for (const other of [...slots.values()]) retire(other, blocked);
|
|
116
|
+
}
|
|
117
|
+
function stopAll(why) {
|
|
118
|
+
const proposed = revision, wasDead = dead;
|
|
119
|
+
for (const s of [...slots.values()]) {
|
|
120
|
+
retire(s, why);
|
|
121
|
+
if (revision !== proposed || dead !== wasDead) return false;
|
|
122
|
+
}
|
|
123
|
+
if (calibration) { calibration.cancel(); calibration = null; }
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
function gatesKnown() { return context && FLAGS.every(k => typeof context[k] === "boolean") && (context.openInstance === null || typeof context.openInstance === "string" && context.openInstance.length >= 1 && context.openInstance.length <= 128); }
|
|
127
|
+
function eligible() { return gatesKnown() && !context.safe && !context.publicLink && !context.reducedMotion && !context.saveData && context.enabled && context.show && context.previewReady && context.optIn; }
|
|
128
|
+
const globalAllowed = () => eligible() && host.document.visibilityState === "visible";
|
|
129
|
+
const signature = d => d.identity;
|
|
130
|
+
function view() {
|
|
131
|
+
return descriptors.map(d => {
|
|
132
|
+
const r = results.get(d.key) ?? { verdict: "off", reason: blocked ?? "unknown", queuePos: null }, s = slots.get(d.key);
|
|
133
|
+
return { key: d.key, id: d.id, ...r, phase: s?.frame ? s.ready ? "running" : "starting" : s ? "loading" : null,
|
|
134
|
+
quality: s?.budget?.snapshot().quality ?? null, reason: blocked ?? ((paused.get(d.key) === signature(d)) ? "paused" : failures.get(d.key)?.identity === signature(d) ? failures.get(d.key).reason : s && context?.hidden ? "hidden" : r.reason) };
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function schedule() {
|
|
138
|
+
if (dead || blocked || !slots.size) return;
|
|
139
|
+
if (raf === null && host.document.visibilityState === "visible" && context?.hidden === false) raf = host.requestAnimationFrame(tick);
|
|
140
|
+
if (timer === null) {
|
|
141
|
+
const waits = [...slots.values()].filter(s => s.hiddenSince !== null).map(s => Math.max(1, 10000 - (last - s.hiddenSince)));
|
|
142
|
+
timer = host.setTimeout(heartbeat, Math.min(1000, ...waits));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const invalidate = () => {
|
|
146
|
+
if (dead || reentrantStock()) return;
|
|
147
|
+
hideGeometry(); for (const s of slots.values()) s.pointer = null;
|
|
148
|
+
if (time() === null) { blocked = "clock_unavailable"; stopAll(blocked); tell(); return; }
|
|
149
|
+
reconcile();
|
|
150
|
+
};
|
|
151
|
+
function observe() {
|
|
152
|
+
const nodes = host.document.querySelectorAll("[data-trust]");
|
|
153
|
+
if (nodes.length > 128) throw Error("trust coverage unavailable");
|
|
154
|
+
const wanted = new Set([host.document.documentElement, ...nodes]);
|
|
155
|
+
for (const d of descriptors) if (d.element?.ownerDocument === host.document && d.element.isConnected === true) wanted.add(d.element);
|
|
156
|
+
for (const n of observed) if (!wanted.has(n)) { resize.unobserve(n); observed.delete(n); }
|
|
157
|
+
for (const n of wanted) if (!observed.has(n)) { resize.observe(n); observed.add(n); }
|
|
158
|
+
|
|
159
|
+
}
|
|
160
|
+
function reconcile() {
|
|
161
|
+
if (dead || reentrantStock()) return;
|
|
162
|
+
const proposal = revision;
|
|
163
|
+
if (time() === null) blocked = "clock_unavailable";
|
|
164
|
+
const hidden = context?.hidden === true || host.document.visibilityState !== "visible";
|
|
165
|
+
const stock = eligible() ? coverage() : null;
|
|
166
|
+
if (blocked || !eligible() || !stock) {
|
|
167
|
+
if (!stopAll(blocked ?? "gates_unknown")) return;
|
|
168
|
+
results.clear(); tell(); return;
|
|
169
|
+
}
|
|
170
|
+
// Hidden frames already owned may survive for <=10 s, but no new frame or load is admitted while hidden.
|
|
171
|
+
const ctx = { ...context, hidden: false };
|
|
172
|
+
const ds = [];
|
|
173
|
+
for (const d of descriptors) {
|
|
174
|
+
if (dead || proposal !== revision) return;
|
|
175
|
+
const allowed = permits(d, stock);
|
|
176
|
+
if (dead || proposal !== revision) return;
|
|
177
|
+
ds.push({ ...d, paused: !allowed || d.paused === true || paused.get(d.key) === signature(d) || failures.get(d.key)?.identity === signature(d) });
|
|
178
|
+
}
|
|
179
|
+
const allocation = allocateEffectFrames(ds, ctx); results.clear();
|
|
180
|
+
for (const r of allocation) {
|
|
181
|
+
const d = descriptors.find(d => d.key === r.key);
|
|
182
|
+
if (r.reason === "paused" && d && d.paused !== true && paused.get(d.key) !== signature(d) && failures.get(d.key)?.identity !== signature(d)) {
|
|
183
|
+
const allowed = permits(d, stock);
|
|
184
|
+
if (dead || proposal !== revision) return;
|
|
185
|
+
if (!allowed) r.reason = d.trialStage ? "trial_ended" : stock.front ? "owner_css" : "stock_overlay";
|
|
186
|
+
}
|
|
187
|
+
results.set(r.key, r);
|
|
188
|
+
}
|
|
189
|
+
if (dead || proposal !== revision) return;
|
|
190
|
+
const chosen = new Set(allocation.filter(r => r.verdict === "active").map(r => r.key));
|
|
191
|
+
for (const s of [...slots.values()]) {
|
|
192
|
+
const d = descriptors.find(x => x.key === s.d.key);
|
|
193
|
+
if (!chosen.has(s.d.key) || !d || signature(d) !== signature(s.d) || d.element !== s.d.element || s.device !== device()) retire(s, "superseded");
|
|
194
|
+
if (dead || proposal !== revision) return;
|
|
195
|
+
}
|
|
196
|
+
if (chosen.size === 0) { if (calibration) { calibration.cancel(); calibration = null; } baseline = null; baselineDevice = null; tell(); return; }
|
|
197
|
+
if (hidden) {
|
|
198
|
+
hideGeometry();
|
|
199
|
+
if (calibration) { calibration.cancel(); calibration = null; }
|
|
200
|
+
for (const s of [...slots.values()]) {
|
|
201
|
+
if (!s.frame) retire(s, "hidden");
|
|
202
|
+
else { if (s.hiddenSince === null) { s.hiddenSince = last; s.healthySince = null; } if (s.budget.gates({ hidden: true, reducedMotion: false, saveData: false, show: true }).remove) retire(s, "hidden"); }
|
|
203
|
+
if (dead || proposal !== revision) return;
|
|
204
|
+
}
|
|
205
|
+
schedule(); tell(); return;
|
|
206
|
+
}
|
|
207
|
+
if (!globalAllowed()) { stopAll("gates_unknown"); tell(); return; }
|
|
208
|
+
for (const s of [...slots.values()]) {
|
|
209
|
+
if (s.frame && s.hiddenSince !== null) { s.hiddenSince = null; s.healthySince = last; if (s.budget.gates({ hidden: false, reducedMotion: false, saveData: false, show: true }).remove) retire(s, "hidden"); }
|
|
210
|
+
if (dead || proposal !== revision) return;
|
|
211
|
+
}
|
|
212
|
+
try { observe(); } catch { stopAll("trust_geometry_unknown"); tell(); return; }
|
|
213
|
+
if (baseline === null || baselineDevice !== device()) {
|
|
214
|
+
if (calibration && baselineDevice === device()) { tell(); return; }
|
|
215
|
+
if (!stopAll("calibrating") || dead || proposal !== revision) return;
|
|
216
|
+
const epoch = ++serial;
|
|
217
|
+
const job = captureEffectBaseline({ window: host, empty: () => !dead && slots.size === 0 && host.document.querySelectorAll("iframe.agend-effect-frame").length === 0 && globalAllowed() && !context.hidden && !!coverage(), clock: nativeClock });
|
|
218
|
+
calibration = job; baselineDevice = device();
|
|
219
|
+
job.promise.then(r => {
|
|
220
|
+
if (dead || calibration !== job || epoch !== serial) return;
|
|
221
|
+
calibration = null;
|
|
222
|
+
if (!r.ok) { blocked = r.reason; tell(); return; }
|
|
223
|
+
baseline = r.baselineFps; reconcile();
|
|
224
|
+
});
|
|
225
|
+
tell(); return;
|
|
226
|
+
}
|
|
227
|
+
const max = context.constrained ? 1 : 3;
|
|
228
|
+
for (const d of descriptors) {
|
|
229
|
+
if (!chosen.has(d.key) || slots.has(d.key)) continue;
|
|
230
|
+
if (slots.size >= max || loads.size >= max) break;
|
|
231
|
+
const permitted = receipt(safety.allowed, d.id, device());
|
|
232
|
+
if (proposal !== revision) return;
|
|
233
|
+
if (permitted !== true) { results.set(d.key, { verdict: "off", reason: "safety_unknown", queuePos: null }); continue; }
|
|
234
|
+
if (slots.has(d.key) || slots.size >= max || loads.size >= max || !globalAllowed() || context.hidden || !coverage(d) || proposal !== revision || !descriptors.some(x => x.key === d.key && signature(x) === signature(d) && x.element === d.element)) break;
|
|
235
|
+
const rect = position(d, coverage(d));
|
|
236
|
+
if (dead || proposal !== revision) return;
|
|
237
|
+
if (!rect || d.element.ownerDocument !== host.document) { results.set(d.key, { verdict: "off", reason: "geometry_unknown", queuePos: null }); continue; }
|
|
238
|
+
const s = { d, device: device(), owner: null, live: true, stageOff: null, ready: false, readyAt: null, frame: null, geometry: null, budget: null, token: null, request: null, loadOff: null, rect: null, pointer: null, lastPointer: null, hiddenSince: null, healthySince: null };
|
|
239
|
+
slots.set(d.key, s);
|
|
240
|
+
if (d.trialStage) {
|
|
241
|
+
s.stageOff = watchEffectTrialStage(d.trialStage, () => {
|
|
242
|
+
if (!current(s)) return;
|
|
243
|
+
const before = revision; retire(s, "trial_ended");
|
|
244
|
+
if (!dead && before === revision) { reconcile(); tell(); }
|
|
245
|
+
});
|
|
246
|
+
if (!s.stageOff || !current(s)) { retire(s, "trial_ended"); continue; }
|
|
247
|
+
}
|
|
248
|
+
const request = { controller: new host.AbortController(), slot: s, timer: null, began: time() }; s.request = request; loads.add(request);
|
|
249
|
+
const withinDeadline = () => {
|
|
250
|
+
const n = time();
|
|
251
|
+
if (n === null || request.began === null || n - request.began >= 10000) { retire(s, n === null || request.began === null ? "clock_unavailable" : "resource_timeout"); return false; }
|
|
252
|
+
return true;
|
|
253
|
+
};
|
|
254
|
+
const acceptCompletion = () => {
|
|
255
|
+
if (!current(s)) { clearMarker(s, "superseded"); return false; }
|
|
256
|
+
if (!withinDeadline()) return false;
|
|
257
|
+
if (request.controller.signal.aborted || !globalAllowed() || context.hidden || !coverage(d)) {
|
|
258
|
+
if (current(s)) retire(s, "admission_revoked"); else clearMarker(s, "superseded");
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
return withinDeadline();
|
|
262
|
+
};
|
|
263
|
+
const deadline = () => {
|
|
264
|
+
if (!current(s)) return;
|
|
265
|
+
const n = time();
|
|
266
|
+
if (n === null || n - request.began >= 10000) { retire(s, n === null ? "clock_unavailable" : "resource_timeout"); tell(); return; }
|
|
267
|
+
request.timer = host.setTimeout(deadline, Math.max(1, 10000 - (n - request.began)));
|
|
268
|
+
};
|
|
269
|
+
request.timer = host.setTimeout(deadline, 10000);
|
|
270
|
+
// Keep a physical read reservation until its promise settles, including after abort/revocation.
|
|
271
|
+
Promise.resolve().then(() => {
|
|
272
|
+
if (!current(s) || request.controller.signal.aborted) return null;
|
|
273
|
+
return load({ key: d.key, id: d.id, params: { ...d.params } }, request.controller.signal);
|
|
274
|
+
}).then(resource => {
|
|
275
|
+
if (!acceptCompletion()) return;
|
|
276
|
+
if (!plain(resource) || resource.id !== d.id || !plain(resource.params) || Object.keys(d.params).some(k => resource.params[k] !== d.params[k])) { retire(s, "resource_unknown"); return; }
|
|
277
|
+
let fresh = position(d, coverage(d));
|
|
278
|
+
if (!fresh || receipt(safety.allowed, d.id, s.device) !== true) { retire(s, "safety_unknown"); return; }
|
|
279
|
+
if (!acceptCompletion()) return;
|
|
280
|
+
const token = receipt(safety.begin, { key: d.key, id: d.id, device: s.device });
|
|
281
|
+
if (typeof token !== "string" || token.length < 1 || token.length > 128) { retire(s, "safety_unknown"); return; }
|
|
282
|
+
s.token = token;
|
|
283
|
+
if (!acceptCompletion()) return;
|
|
284
|
+
// Obtain local geometry BEFORE the factory appends its temporarily unknown iframe. bind() is
|
|
285
|
+
// the very next local-stage operation and registers that frame before checking its topology.
|
|
286
|
+
const local = d.trialStage ? localStage(d, coverage(d)) : null;
|
|
287
|
+
if (d.trialStage && !local || !current(s)) { if (current(s)) retire(s, "admission_revoked"); return; }
|
|
288
|
+
if (local) fresh = local.rect;
|
|
289
|
+
s.rect = fresh;
|
|
290
|
+
s.budget = new EffectBudget({ device: s.device, baselineFps: baseline, clock: nativeClock, longTasksSupported });
|
|
291
|
+
const frame = createEffectFrame({ window: host, root: local?.root ?? root, origin, boot, clock: nativeClock,
|
|
292
|
+
render: { code: resource.code, params: resource.params, assets: resource.assets, wants: resource.wants, instance: d.instance, width: fresh.width, height: fresh.height, dpr: dpr(s.device) },
|
|
293
|
+
onReady() { if (!current(s)) return; if (!globalAllowed() || context.hidden || !coverage(d)) { retire(s, "hidden"); tell(); return; } const n = time(); if (n === null) { blocked = "clock_unavailable"; stopAll(blocked); tell(); return; } s.ready = true; s.readyAt = n; s.healthySince = n; publishEnv(s); schedule(); tell(); },
|
|
294
|
+
onRemove(why) { if (current(s)) { retire(s, why); tell(); } },
|
|
295
|
+
});
|
|
296
|
+
s.frame = frame;
|
|
297
|
+
if (!current(s) || !frame.ok || frame.state().removed) { frame.remove?.("stale_mount"); retire(s, "mount_failed"); return; }
|
|
298
|
+
frame.element.id = `agend-effect-${boot}-${++frameSerial}`;
|
|
299
|
+
s.geometry = local ? local.bind(frame.element, { layer: d.layer }, coverage()) : geometry.bind(frame.element, { layer: d.layer, by: d.by });
|
|
300
|
+
if (!s.geometry.ok) { retire(s, "geometry_unknown"); return; }
|
|
301
|
+
const loaded = () => { if (current(s)) s.budget.loaded(); };
|
|
302
|
+
frame.element.addEventListener("load", loaded); s.loadOff = () => frame.element.removeEventListener("load", loaded);
|
|
303
|
+
const paint = coverage(d);
|
|
304
|
+
if (!current(s) || !paint || !syncGeometry(s, paint)) { retire(s, "geometry_unknown"); return; }
|
|
305
|
+
// One non-serializable host receipt per actual admitted frame. Never append it to a Worker event.
|
|
306
|
+
const owner = Object.freeze({ key: d.key, id: d.id, device: s.device, current: () => current(s) && s.owner === owner });
|
|
307
|
+
s.owner = owner;
|
|
308
|
+
try { onMount(owner); } catch { /* diagnostics/display failure cannot authorize or mutate a mount */ }
|
|
309
|
+
schedule();
|
|
310
|
+
}).catch(() => { if (current(s)) retire(s, "resource_unavailable"); }).finally(() => {
|
|
311
|
+
if (request.timer !== null) host.clearTimeout(request.timer);
|
|
312
|
+
loads.delete(request); if (s.request === request) s.request = null;
|
|
313
|
+
if (!dead) reconcile(); else if (loads.size === 0 && registries.get(host) === api) registries.delete(host);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
schedule(); tell();
|
|
317
|
+
}
|
|
318
|
+
function publishEnv(s, proof = coverage(s.d)) {
|
|
319
|
+
if (!s.ready || !current(s) || context.hidden || !globalAllowed() || !permits(s.d, proof)) return;
|
|
320
|
+
const e = effectEvent("env", { width: s.rect.width, height: s.rect.height, dpr: dpr(s.device), quality: s.budget.snapshot().quality }, null, time());
|
|
321
|
+
if (e) s.frame.send(e);
|
|
322
|
+
}
|
|
323
|
+
function tick() {
|
|
324
|
+
raf = null;
|
|
325
|
+
if (dead) return;
|
|
326
|
+
const paint = coverage();
|
|
327
|
+
if (!globalAllowed() || context.hidden || !paint) { invalidate(); return; }
|
|
328
|
+
const proposal = revision; let policyChanged = false;
|
|
329
|
+
for (const s of [...slots.values()]) {
|
|
330
|
+
if (dead || proposal !== revision) return;
|
|
331
|
+
if (!s.frame) continue;
|
|
332
|
+
if (!permits(s.d, paint)) { retire(s, "superseded"); policyChanged = true; continue; }
|
|
333
|
+
if (dead || proposal !== revision) return;
|
|
334
|
+
const rect = syncGeometry(s, paint);
|
|
335
|
+
if (!current(s)) continue;
|
|
336
|
+
if (!rect) { retire(s, "geometry_unknown"); continue; }
|
|
337
|
+
// The host owns the CSS viewport. A bounded env signal lets Worker code redraw its own bitmap if desired.
|
|
338
|
+
const changed = rect.width !== s.rect.width || rect.height !== s.rect.height; s.rect = rect;
|
|
339
|
+
if (changed) publishEnv(s, paint);
|
|
340
|
+
if (s.ready) s.budget.frame();
|
|
341
|
+
if (s.pointer) {
|
|
342
|
+
const packet = s.pointer; s.pointer = null;
|
|
343
|
+
const n = time(), b = s.budget.snapshot();
|
|
344
|
+
if (n !== null && n - packet.at <= 100 && permits(s.d, paint) && pointerGate(paint) && current(s) && b.forward && (s.lastPointer === null || n - s.lastPointer >= 1000 / b.pointerHz)) {
|
|
345
|
+
const event = effectEvent("pointer", packet.fields, s.d.instance, packet.at);
|
|
346
|
+
if (event && s.frame.send(event)) s.lastPointer = n;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (dead || proposal !== revision) return;
|
|
351
|
+
if (policyChanged) reconcile(); else schedule();
|
|
352
|
+
}
|
|
353
|
+
function heartbeat() {
|
|
354
|
+
timer = null;
|
|
355
|
+
if (dead) return;
|
|
356
|
+
if (time() === null) { blocked = "clock_unavailable"; stopAll(blocked); tell(); return; }
|
|
357
|
+
const proposal = revision, activeBeforeSafety = slots.size;
|
|
358
|
+
if (!checkSafety()) { tell(); return; }
|
|
359
|
+
if (slots.size !== activeBeforeSafety) reconcile();
|
|
360
|
+
if (dead || proposal !== revision) return;
|
|
361
|
+
if (!globalAllowed() || context.hidden || !coverage()) { invalidate(); return; }
|
|
362
|
+
const budgetPauses = new Set();
|
|
363
|
+
for (const s of [...slots.values()]) {
|
|
364
|
+
if (dead || proposal !== revision) return;
|
|
365
|
+
if (!current(s) || !s.frame || !s.ready) continue;
|
|
366
|
+
if (s.d.trialStage && !coverage(s.d)) { if (dead || proposal !== revision) return; if (current(s)) retire(s, "trial_ended"); continue; }
|
|
367
|
+
if (dead || proposal !== revision) return;
|
|
368
|
+
const r = s.budget.gates({ hidden: false, reducedMotion: context.reducedMotion, saveData: context.saveData, show: context.show });
|
|
369
|
+
const state = r.remove ? r : s.readyAt !== null && last - s.readyAt >= 3000 ? s.budget.poll() : r;
|
|
370
|
+
try { onProfile({ key: s.d.key, id: s.d.id, device: s.device, ...state }, s.owner); } catch { /* metrics never authorize a mount */ }
|
|
371
|
+
if (dead || proposal !== revision) return;
|
|
372
|
+
if (!current(s)) continue;
|
|
373
|
+
if (s.d.trialStage && !coverage(s.d)) { if (dead || proposal !== revision) return; if (current(s)) retire(s, "trial_ended"); continue; }
|
|
374
|
+
if (dead || proposal !== revision) return;
|
|
375
|
+
if (state.remove) { const key = s.d.id + ":" + s.device; const count = state.reason === "host_budget" && !budgetPauses.has(key); if (count) budgetPauses.add(key); retire(s, state.reason, count); continue; }
|
|
376
|
+
if (state.badWindows > 0) s.healthySince = null; else if (s.healthySince === null) s.healthySince = last;
|
|
377
|
+
if (s.token && s.healthySince !== null && last - s.healthySince >= 5000 && state.badWindows === 0 && state.metrics !== null && !clearMarker(s, "healthy")) { stopAll("safety_unknown"); break; }
|
|
378
|
+
publishEnv(s);
|
|
379
|
+
}
|
|
380
|
+
if (dead || proposal !== revision) return;
|
|
381
|
+
schedule(); tell();
|
|
382
|
+
}
|
|
383
|
+
function checkSafety() {
|
|
384
|
+
if (dead || blocked) return false;
|
|
385
|
+
const proposed = revision;
|
|
386
|
+
for (const s of [...slots.values()]) {
|
|
387
|
+
if (!current(s)) continue;
|
|
388
|
+
const allowed = receipt(safety.allowed, s.d.id, s.device);
|
|
389
|
+
if (dead || proposed !== revision) return false;
|
|
390
|
+
if (current(s) && allowed !== true) retire(s, "safety_revoked");
|
|
391
|
+
if (dead || blocked || proposed !== revision) return false;
|
|
392
|
+
}
|
|
393
|
+
return !dead && !blocked;
|
|
394
|
+
}
|
|
395
|
+
function revalidateSafety() {
|
|
396
|
+
if (dead || reentrantStock()) return view();
|
|
397
|
+
if (checkSafety()) reconcile(); else tell();
|
|
398
|
+
return view();
|
|
399
|
+
}
|
|
400
|
+
function pointerGate(proof = coverage()) {
|
|
401
|
+
if (!globalAllowed() || context.hidden || context.modal || !proof || proof.front !== true) return false;
|
|
402
|
+
try {
|
|
403
|
+
const focused = host.document.activeElement;
|
|
404
|
+
return host.document.designMode !== "on" && !!focused && ["BODY", "BUTTON", "A"].includes(focused.tagName) && focused.isContentEditable === false && typeof focused.matches === "function" && !focused.matches("input,textarea,select,[contenteditable]:not([contenteditable='false'])");
|
|
405
|
+
} catch { return false; }
|
|
406
|
+
}
|
|
407
|
+
function pointer(event) {
|
|
408
|
+
if (dead || blocked || !pointerGate() || event.isTrusted !== true) return;
|
|
409
|
+
const kinds = { pointermove: "move", pointerdown: "down", pointerup: "up", click: "click", wheel: "wheel" }, kind = kinds[event.type];
|
|
410
|
+
if (!kind || !finite(event.clientX) || !finite(event.clientY) || Math.abs(event.clientX) > 1e7 || Math.abs(event.clientY) > 1e7 || !Number.isInteger(event.buttons) || event.buttons < 0 || event.buttons > 31) return;
|
|
411
|
+
try { const target = host.document.elementFromPoint(event.clientX, event.clientY); if (!target || typeof target.closest !== "function" || target.closest("[data-trust]")) return; } catch { return; }
|
|
412
|
+
const at = time(); if (at === null) { blocked = "clock_unavailable"; stopAll(blocked); tell(); return; }
|
|
413
|
+
for (const s of slots.values()) {
|
|
414
|
+
if (!s.ready || !s.frame || !s.budget.snapshot().forward || !coverage(s.d) || !current(s)) continue;
|
|
415
|
+
const r = hostEffectRect(s.d.element); if (!r) continue;
|
|
416
|
+
const fields = { kind, x: event.clientX - r.x, y: event.clientY - r.y, buttons: event.buttons };
|
|
417
|
+
if (fields.x < 0 || fields.y < 0 || fields.x > r.width || fields.y > r.height) continue;
|
|
418
|
+
if (kind === "wheel") { fields.dx = event.deltaX; fields.dy = event.deltaY; }
|
|
419
|
+
if (effectEvent("pointer", fields, s.d.instance, at)) s.pointer = { fields, at };
|
|
420
|
+
}
|
|
421
|
+
schedule();
|
|
422
|
+
}
|
|
423
|
+
try {
|
|
424
|
+
if (time() === null) throw Error("clock unavailable");
|
|
425
|
+
root = host.document.createElement("div"); root.className = "agend-effect-layer"; host.document.body.append(root);
|
|
426
|
+
geometry = createEffectGeometry({ window: host }); if (!geometry.ok) throw Error("geometry unsupported");
|
|
427
|
+
observer = new host.MutationObserver(invalidate); observer.observe(host.document.documentElement, { childList: true, subtree: true, attributes: true, characterData: true });
|
|
428
|
+
resize = new host.ResizeObserver(invalidate);
|
|
429
|
+
for (const type of ["scroll", "resize", "orientationchange", "focusin", "focusout"]) { host.addEventListener(type, invalidate, true); offs.push(() => host.removeEventListener(type, invalidate, true)); }
|
|
430
|
+
for (const type of ["pointermove", "pointerdown", "pointerup", "click", "wheel"]) { host.addEventListener(type, pointer, { passive: true }); offs.push(() => host.removeEventListener(type, pointer)); }
|
|
431
|
+
host.addEventListener("storage", revalidateSafety); offs.push(() => host.removeEventListener("storage", revalidateSafety));
|
|
432
|
+
const visibility = () => { revalidateSafety(); invalidate(); };
|
|
433
|
+
host.document.addEventListener("visibilitychange", visibility); offs.push(() => host.document.removeEventListener("visibilitychange", visibility));
|
|
434
|
+
if (typeof host.PerformanceObserver === "function" && host.PerformanceObserver.supportedEntryTypes?.includes("longtask")) {
|
|
435
|
+
try {
|
|
436
|
+
longTasks = new host.PerformanceObserver(list => {
|
|
437
|
+
for (const entry of list.getEntries()) for (const s of slots.values()) if (s.ready && s.budget) {
|
|
438
|
+
// No URL, container name, content or private Worker timing is read. Missing attribution stays unknown.
|
|
439
|
+
const attributed = Array.isArray(entry.attribution) && entry.attribution.some(a => a.containerId === s.frame.element.id && typeof a.containerId === "string" && a.containerId.length > 0);
|
|
440
|
+
s.budget.longTask(entry.startTime, entry.duration, attributed);
|
|
441
|
+
}
|
|
442
|
+
}); longTasks.observe({ type: "longtask", buffered: false }); longTasksSupported = true;
|
|
443
|
+
} catch { longTasks?.disconnect(); longTasks = null; }
|
|
444
|
+
}
|
|
445
|
+
} catch {
|
|
446
|
+
dead = true;
|
|
447
|
+
for (const release of [() => observer?.disconnect(), () => resize?.disconnect(), () => longTasks?.disconnect(), ...offs, () => geometry?.dispose(), () => root?.remove()]) { try { release(); } catch { /* continue releasing other host resources */ } }
|
|
448
|
+
return { ok: false, reason: "registry_unsupported" };
|
|
449
|
+
}
|
|
450
|
+
const api = { ok: true,
|
|
451
|
+
update(ds, ctx) {
|
|
452
|
+
if (dead || reentrantStock()) return view();
|
|
453
|
+
hideGeometry();
|
|
454
|
+
revision++;
|
|
455
|
+
if (!Array.isArray(ds) || ds.length > 128 || !plain(ctx)) { descriptors = []; context = null; results.clear(); stopAll("gates_unknown"); return []; }
|
|
456
|
+
descriptors = ds.map(snapshotDescriptor).filter(Boolean);
|
|
457
|
+
for (const [key, identity] of paused) if (!descriptors.some(d => d.key === key && signature(d) === identity)) paused.delete(key);
|
|
458
|
+
for (const [key, f] of failures) if (!descriptors.some(d => d.key === key && signature(d) === f.identity)) failures.delete(key);
|
|
459
|
+
context = Object.fromEntries([...FLAGS, "openInstance"].map(k => [k, ctx[k]]));
|
|
460
|
+
reconcile(); return view();
|
|
461
|
+
},
|
|
462
|
+
snapshot: view,
|
|
463
|
+
/** Host storage/foreground observation only, never called from a frame message. */
|
|
464
|
+
revalidateSafety,
|
|
465
|
+
/** Called only by the host's explicit human Resume UI. A descriptor update never clears this latch. */
|
|
466
|
+
resume(key) {
|
|
467
|
+
if (dead || reentrantStock() || blocked && !blocked.startsWith("baseline_") || typeof key !== "string") return false;
|
|
468
|
+
const proposed = revision, d = descriptors.find(d => d.key === key); if (!d || d.trialStage && !coverage(d) || revision !== proposed || receipt(safety.resume, d.id, device()) !== true || revision !== proposed) return false;
|
|
469
|
+
revision++;
|
|
470
|
+
if (!stopAll("human_resume")) return false;
|
|
471
|
+
if (blocked && !blocked.startsWith("baseline_")) return false; blocked = null; paused.delete(key); failures.delete(key); baseline = null; serial++; reconcile(); return true;
|
|
472
|
+
},
|
|
473
|
+
/** Host-only content-free signal API. Raw pointer publishing is refused; native pointer metadata is coalesced here. */
|
|
474
|
+
publish(type, fields, instance = null) {
|
|
475
|
+
if (dead || blocked || !globalAllowed() || context.hidden || !coverage() || !TYPES.includes(type)) return 0;
|
|
476
|
+
const ts = time(); if (ts === null) { blocked = "clock_unavailable"; stopAll(blocked); tell(); return 0; }
|
|
477
|
+
let sent = 0; const proposal = revision;
|
|
478
|
+
for (const s of [...slots.values()]) {
|
|
479
|
+
if (dead || proposal !== revision) return sent;
|
|
480
|
+
if (!s.ready || !s.frame || !s.budget.snapshot().forward || !coverage(s.d) || !current(s)) continue;
|
|
481
|
+
if (["state", "reply", "mention"].includes(type) && (s.d.instance !== null ? instance !== s.d.instance : instance !== context.openInstance)) continue;
|
|
482
|
+
const e = effectEvent(type, fields, s.d.instance, ts); if (e && s.frame.send(e)) sent++;
|
|
483
|
+
}
|
|
484
|
+
return sent;
|
|
485
|
+
},
|
|
486
|
+
dispose() {
|
|
487
|
+
if (dead) return; dead = true; revision++; serial++; stopAll("disposed");
|
|
488
|
+
if (raf !== null) host.cancelAnimationFrame(raf); if (timer !== null) host.clearTimeout(timer);
|
|
489
|
+
observer.disconnect(); resize.disconnect(); longTasks?.disconnect(); for (const off of offs) off();
|
|
490
|
+
geometry.dispose(); root.remove(); observed.clear(); if (loads.size === 0 && registries.get(host) === api) registries.delete(host);
|
|
491
|
+
},
|
|
492
|
+
};
|
|
493
|
+
// Read-only host geometry seam; no selector guessing among poster and live roots.
|
|
494
|
+
Object.defineProperty(api, "frameRoot", { get: () => dead ? null : root });
|
|
495
|
+
registries.set(host, api); return api;
|
|
496
|
+
}
|