@songsid/agend 2.3.0-beta.1 → 2.3.0-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.
Files changed (351) hide show
  1. package/dist/agent-cli-instructions.md +5 -2
  2. package/dist/agent-cli.js +43 -7
  3. package/dist/agent-cli.js.map +1 -1
  4. package/dist/agent-endpoint.js +4 -0
  5. package/dist/agent-endpoint.js.map +1 -1
  6. package/dist/agent-file-read.d.ts +14 -6
  7. package/dist/agent-file-read.js +207 -19
  8. package/dist/agent-file-read.js.map +1 -1
  9. package/dist/agent-whats-new.d.ts +11 -0
  10. package/dist/agent-whats-new.js +29 -15
  11. package/dist/agent-whats-new.js.map +1 -1
  12. package/dist/auth-api.d.ts +30 -1
  13. package/dist/auth-api.js +45 -13
  14. package/dist/auth-api.js.map +1 -1
  15. package/dist/authority/authority-state.d.ts +38 -0
  16. package/dist/authority/authority-state.js +114 -0
  17. package/dist/authority/authority-state.js.map +1 -0
  18. package/dist/authority/session-authorizations.d.ts +43 -0
  19. package/dist/authority/session-authorizations.js +102 -0
  20. package/dist/authority/session-authorizations.js.map +1 -0
  21. package/dist/authority/web-authority.d.ts +120 -0
  22. package/dist/authority/web-authority.js +157 -0
  23. package/dist/authority/web-authority.js.map +1 -0
  24. package/dist/backend/claude-code.d.ts +2 -2
  25. package/dist/backend/claude-code.js +10 -5
  26. package/dist/backend/claude-code.js.map +1 -1
  27. package/dist/backend/types.d.ts +14 -0
  28. package/dist/backend/types.js +25 -0
  29. package/dist/backend/types.js.map +1 -1
  30. package/dist/backend-availability.d.ts +120 -0
  31. package/dist/backend-availability.js +396 -0
  32. package/dist/backend-availability.js.map +1 -0
  33. package/dist/backend-command.d.ts +21 -3
  34. package/dist/backend-command.js +23 -4
  35. package/dist/backend-command.js.map +1 -1
  36. package/dist/bundled-skills.d.ts +14 -0
  37. package/dist/bundled-skills.js +29 -0
  38. package/dist/bundled-skills.js.map +1 -0
  39. package/dist/cache-analysis.d.ts +47 -1
  40. package/dist/cache-analysis.js +68 -8
  41. package/dist/cache-analysis.js.map +1 -1
  42. package/dist/cache-ledger.d.ts +16 -3
  43. package/dist/cache-ledger.js +33 -13
  44. package/dist/cache-ledger.js.map +1 -1
  45. package/dist/cache-report-tool.d.ts +17 -0
  46. package/dist/cache-report-tool.js +40 -0
  47. package/dist/cache-report-tool.js.map +1 -0
  48. package/dist/cache-service.d.ts +70 -3
  49. package/dist/cache-service.js +178 -17
  50. package/dist/cache-service.js.map +1 -1
  51. package/dist/channel/adapters/discord.d.ts +7 -0
  52. package/dist/channel/adapters/discord.js +35 -0
  53. package/dist/channel/adapters/discord.js.map +1 -1
  54. package/dist/channel/mcp-tools.js +22 -10
  55. package/dist/channel/mcp-tools.js.map +1 -1
  56. package/dist/channel/types.d.ts +6 -0
  57. package/dist/classic-channel-manager.d.ts +10 -5
  58. package/dist/classic-channel-manager.js +12 -9
  59. package/dist/classic-channel-manager.js.map +1 -1
  60. package/dist/cli.js +130 -31
  61. package/dist/cli.js.map +1 -1
  62. package/dist/config-validator.js +103 -4
  63. package/dist/config-validator.js.map +1 -1
  64. package/dist/daemon.d.ts +7 -0
  65. package/dist/daemon.js +63 -16
  66. package/dist/daemon.js.map +1 -1
  67. package/dist/decision-list.d.ts +25 -0
  68. package/dist/decision-list.js +30 -0
  69. package/dist/decision-list.js.map +1 -0
  70. package/dist/delivery-outbox.d.ts +10 -0
  71. package/dist/delivery-outbox.js +12 -1
  72. package/dist/delivery-outbox.js.map +1 -1
  73. package/dist/fleet-context.d.ts +10 -0
  74. package/dist/fleet-manager.d.ts +417 -11
  75. package/dist/fleet-manager.js +1387 -109
  76. package/dist/fleet-manager.js.map +1 -1
  77. package/dist/fleet-traffic.d.ts +74 -0
  78. package/dist/fleet-traffic.js +196 -0
  79. package/dist/fleet-traffic.js.map +1 -0
  80. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +20 -5
  81. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
  82. package/dist/general-knowledge/skills/multi-channel/SKILL.md +3 -2
  83. package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
  84. package/dist/general-knowledge/skills/web-appearance/SKILL.md +257 -23
  85. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +476 -0
  86. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
  87. package/dist/human-delivery-receipts.d.ts +24 -1
  88. package/dist/human-delivery-receipts.js +64 -11
  89. package/dist/human-delivery-receipts.js.map +1 -1
  90. package/dist/instance-config-impact.js +3 -1
  91. package/dist/instance-config-impact.js.map +1 -1
  92. package/dist/instance-names.d.ts +4 -0
  93. package/dist/instance-names.js +12 -0
  94. package/dist/instance-names.js.map +1 -0
  95. package/dist/instructions.d.ts +12 -1
  96. package/dist/instructions.js +13 -2
  97. package/dist/instructions.js.map +1 -1
  98. package/dist/locale.js +100 -8
  99. package/dist/locale.js.map +1 -1
  100. package/dist/ls-attach-resolve.d.ts +95 -0
  101. package/dist/ls-attach-resolve.js +174 -0
  102. package/dist/ls-attach-resolve.js.map +1 -0
  103. package/dist/memory-hogs.d.ts +27 -0
  104. package/dist/memory-hogs.js +124 -0
  105. package/dist/memory-hogs.js.map +1 -0
  106. package/dist/outbound-handlers.d.ts +10 -0
  107. package/dist/outbound-handlers.js +122 -2
  108. package/dist/outbound-handlers.js.map +1 -1
  109. package/dist/outbound-schemas.d.ts +164 -4
  110. package/dist/outbound-schemas.js +99 -3
  111. package/dist/outbound-schemas.js.map +1 -1
  112. package/dist/public-web-gateway.js +8 -2
  113. package/dist/public-web-gateway.js.map +1 -1
  114. package/dist/public-web-link.d.ts +9 -0
  115. package/dist/public-web-link.js +8 -0
  116. package/dist/public-web-link.js.map +1 -1
  117. package/dist/quickstart-api.d.ts +10 -1
  118. package/dist/quickstart-api.js +12 -4
  119. package/dist/quickstart-api.js.map +1 -1
  120. package/dist/reply-buttons-controller.d.ts +4 -0
  121. package/dist/reply-buttons-controller.js +2 -2
  122. package/dist/reply-buttons-controller.js.map +1 -1
  123. package/dist/restart-progress.d.ts +65 -1
  124. package/dist/restart-progress.js +236 -10
  125. package/dist/restart-progress.js.map +1 -1
  126. package/dist/rollout-index.d.ts +5 -0
  127. package/dist/rollout-index.js +26 -5
  128. package/dist/rollout-index.js.map +1 -1
  129. package/dist/scheduler/scheduler.d.ts +12 -0
  130. package/dist/scheduler/scheduler.js +83 -10
  131. package/dist/scheduler/scheduler.js.map +1 -1
  132. package/dist/service-installer.d.ts +2 -1
  133. package/dist/service-installer.js +18 -3
  134. package/dist/service-installer.js.map +1 -1
  135. package/dist/settings-api.d.ts +19 -0
  136. package/dist/settings-api.js +137 -2
  137. package/dist/settings-api.js.map +1 -1
  138. package/dist/settings-change.js +104 -0
  139. package/dist/settings-change.js.map +1 -1
  140. package/dist/settings-effect.js +17 -1
  141. package/dist/settings-effect.js.map +1 -1
  142. package/dist/tmux-manager.d.ts +5 -0
  143. package/dist/tmux-manager.js +16 -0
  144. package/dist/tmux-manager.js.map +1 -1
  145. package/dist/tool-permissions.d.ts +4 -6
  146. package/dist/tool-permissions.js +53 -5
  147. package/dist/tool-permissions.js.map +1 -1
  148. package/dist/topic-commands.d.ts +7 -1
  149. package/dist/topic-commands.js +18 -4
  150. package/dist/topic-commands.js.map +1 -1
  151. package/dist/types.d.ts +48 -1
  152. package/dist/ui/app-effects.js +77 -31
  153. package/dist/ui/chat-ambient.js +307 -0
  154. package/dist/ui/chat-effect-posters.js +8 -8
  155. package/dist/ui/chat-effects.js +101 -13
  156. package/dist/ui/chat-render.js +5 -2
  157. package/dist/ui/chat-strings.js +59 -6
  158. package/dist/ui/chat-thread.js +149 -21
  159. package/dist/ui/effect-trial-controller.js +13 -8
  160. package/dist/ui/effect-trial-panel.js +19 -16
  161. package/dist/ui/fleet-cache.js +23 -1
  162. package/dist/ui/fleet-canvas-layout.js +111 -0
  163. package/dist/ui/fleet-canvas.js +525 -0
  164. package/dist/ui/fleet-org.js +27 -12
  165. package/dist/ui/fleet-traffic.js +343 -0
  166. package/dist/ui/fleet-usage.js +401 -0
  167. package/dist/ui/panel-chat.js +127 -49
  168. package/dist/ui/panel-details.js +2 -1
  169. package/dist/ui/panel-fleet.js +41 -16
  170. package/dist/ui/panel-settings.js +26 -8
  171. package/dist/ui/preview.js +3 -2
  172. package/dist/ui/settings-appearance.js +42 -18
  173. package/dist/ui/settings-catalog.js +92 -0
  174. package/dist/ui/settings-dialogs.js +40 -12
  175. package/dist/ui/settings-effects.js +4 -3
  176. package/dist/ui/settings-guests.js +117 -0
  177. package/dist/ui/settings-learn.js +25 -0
  178. package/dist/ui/settings-model.js +15 -6
  179. package/dist/ui/settings-mods.js +23 -9
  180. package/dist/ui/settings-owner-effects.js +9 -9
  181. package/dist/ui/settings-strings.js +120 -42
  182. package/dist/ui/settings-web-authority.js +91 -0
  183. package/dist/ui/shared/agend-auth.js +1 -1
  184. package/dist/ui/shared/app-i18n.js +4 -2
  185. package/dist/ui/shared/app-prefs.js +5 -4
  186. package/dist/ui/shared/app-route.js +1 -1
  187. package/dist/ui/shared/app-session.js +2 -0
  188. package/dist/ui/shared/app-shell.js +75 -9
  189. package/dist/ui/shared/app-store.js +26 -1
  190. package/dist/ui/shared/app-stream.js +3 -1
  191. package/dist/ui/shared/app-themes.js +24 -6
  192. package/dist/ui/shared/app.css +364 -16
  193. package/dist/ui/shared/app.js +21 -14
  194. package/dist/ui/shared/card-strings.js +4 -0
  195. package/dist/ui/shared/effect-app-runtime.js +68 -19
  196. package/dist/ui/shared/effect-chat-runtime.js +16 -3
  197. package/dist/ui/shared/effect-frame.js +26 -7
  198. package/dist/ui/shared/effect-geometry.js +24 -9
  199. package/dist/ui/shared/effect-policy.js +21 -7
  200. package/dist/ui/shared/effect-profiler.js +31 -9
  201. package/dist/ui/shared/effect-registry.js +87 -48
  202. package/dist/ui/shared/effect-stock-snapshot.js +9 -0
  203. package/dist/ui/shared/effect-stock-trust.js +62 -10
  204. package/dist/ui/shared/effect-trial-shadow.js +62 -0
  205. package/dist/ui/shared/effect-trial-source.js +1 -1
  206. package/dist/ui/shared/effect-trial-stage.js +54 -31
  207. package/dist/ui/shared/effect-trial.css +40 -0
  208. package/dist/ui/shared/fx-motion.js +67 -0
  209. package/dist/ui/shared/identity-card.js +75 -4
  210. package/dist/ui/shared/identity-color.js +3 -3
  211. package/dist/ui/shared/instance-nav.js +194 -28
  212. package/dist/ui/shared/panel-view.js +24 -4
  213. package/dist/ui/shared/tokens.css +9 -3
  214. package/dist/ui/shared/ui-icons.js +1 -0
  215. package/dist/ui/shared/usage-dialog.js +22 -2
  216. package/dist/ui/shared/view-strings.js +68 -2
  217. package/dist/ui/shared/web-authorize.js +151 -0
  218. package/dist/ui/signin.html +1 -1
  219. package/dist/ui/web-terminal/terminal.css +4 -4
  220. package/dist/usage/providers.d.ts +28 -0
  221. package/dist/usage/providers.js +126 -36
  222. package/dist/usage/providers.js.map +1 -1
  223. package/dist/usage-report.d.ts +158 -0
  224. package/dist/usage-report.js +226 -0
  225. package/dist/usage-report.js.map +1 -0
  226. package/dist/usage-rollup.d.ts +51 -0
  227. package/dist/usage-rollup.js +168 -0
  228. package/dist/usage-rollup.js.map +1 -0
  229. package/dist/view-api.d.ts +39 -0
  230. package/dist/view-api.js +65 -4
  231. package/dist/view-api.js.map +1 -1
  232. package/dist/web-ambient.d.ts +72 -0
  233. package/dist/web-ambient.js +161 -0
  234. package/dist/web-ambient.js.map +1 -0
  235. package/dist/web-api.d.ts +69 -5
  236. package/dist/web-api.js +362 -45
  237. package/dist/web-api.js.map +1 -1
  238. package/dist/web-appearance-policy.d.ts +4 -6
  239. package/dist/web-appearance-policy.js +5 -12
  240. package/dist/web-appearance-policy.js.map +1 -1
  241. package/dist/web-appearance-tools.d.ts +3 -0
  242. package/dist/web-appearance-tools.js +22 -3
  243. package/dist/web-appearance-tools.js.map +1 -1
  244. package/dist/web-appearance-view.d.ts +27 -2
  245. package/dist/web-appearance-view.js +129 -14
  246. package/dist/web-appearance-view.js.map +1 -1
  247. package/dist/web-appearance.d.ts +17 -3
  248. package/dist/web-appearance.js +82 -7
  249. package/dist/web-appearance.js.map +1 -1
  250. package/dist/web-asset-parts.d.ts +5 -10
  251. package/dist/web-asset-parts.js +3 -3
  252. package/dist/web-asset-parts.js.map +1 -1
  253. package/dist/web-auth.js +3 -1
  254. package/dist/web-auth.js.map +1 -1
  255. package/dist/web-card.d.ts +14 -0
  256. package/dist/web-card.js +40 -6
  257. package/dist/web-card.js.map +1 -1
  258. package/dist/web-channel-echo.d.ts +7 -1
  259. package/dist/web-channel-echo.js +6 -2
  260. package/dist/web-channel-echo.js.map +1 -1
  261. package/dist/web-chat-history.d.ts +46 -0
  262. package/dist/web-chat-history.js +80 -3
  263. package/dist/web-chat-history.js.map +1 -1
  264. package/dist/web-commands.d.ts +1 -0
  265. package/dist/web-effect-bootstrap.d.ts +12 -0
  266. package/dist/web-effect-bootstrap.js +49 -0
  267. package/dist/web-effect-bootstrap.js.map +1 -0
  268. package/dist/web-effect-bundle.js +46 -1
  269. package/dist/web-effect-bundle.js.map +1 -1
  270. package/dist/web-effect-playback.d.ts +30 -0
  271. package/dist/web-effect-playback.js +71 -0
  272. package/dist/web-effect-playback.js.map +1 -0
  273. package/dist/web-effect-profiles.d.ts +4 -4
  274. package/dist/web-effect-service.d.ts +11 -4
  275. package/dist/web-effect-service.js +28 -5
  276. package/dist/web-effect-service.js.map +1 -1
  277. package/dist/web-effect-tools.d.ts +1 -0
  278. package/dist/web-effect-tools.js +8 -5
  279. package/dist/web-effect-tools.js.map +1 -1
  280. package/dist/web-effect-trials.d.ts +4 -4
  281. package/dist/web-effect-trials.js +6 -6
  282. package/dist/web-effect-trials.js.map +1 -1
  283. package/dist/web-effect-upload.d.ts +2 -4
  284. package/dist/web-effect-upload.js +3 -8
  285. package/dist/web-effect-upload.js.map +1 -1
  286. package/dist/web-effect-worker.d.ts +1 -1
  287. package/dist/web-effect-worker.js +2 -2
  288. package/dist/web-effect-worker.js.map +1 -1
  289. package/dist/web-effect.d.ts +14 -0
  290. package/dist/web-effect.js +21 -0
  291. package/dist/web-effect.js.map +1 -1
  292. package/dist/web-guest-access.d.ts +125 -0
  293. package/dist/web-guest-access.js +234 -0
  294. package/dist/web-guest-access.js.map +1 -0
  295. package/dist/web-guest-rooms.d.ts +82 -0
  296. package/dist/web-guest-rooms.js +135 -0
  297. package/dist/web-guest-rooms.js.map +1 -0
  298. package/dist/web-identity-color.d.ts +1 -1
  299. package/dist/web-identity-color.js +2 -2
  300. package/dist/web-identity-color.js.map +1 -1
  301. package/dist/web-identity-palette.d.ts +1 -1
  302. package/dist/web-identity-palette.js +2 -2
  303. package/dist/web-identity-palette.js.map +1 -1
  304. package/dist/web-login.d.ts +47 -0
  305. package/dist/web-login.js +115 -0
  306. package/dist/web-login.js.map +1 -1
  307. package/dist/web-mod-agent.d.ts +1 -1
  308. package/dist/web-mod-agent.js +12 -8
  309. package/dist/web-mod-agent.js.map +1 -1
  310. package/dist/web-mod-bundle.d.ts +14 -1
  311. package/dist/web-mod-bundle.js +68 -3
  312. package/dist/web-mod-bundle.js.map +1 -1
  313. package/dist/web-mod-export.d.ts +9 -0
  314. package/dist/web-mod-export.js +21 -0
  315. package/dist/web-mod-export.js.map +1 -1
  316. package/dist/web-mod-store.d.ts +7 -1
  317. package/dist/web-mod-store.js +21 -7
  318. package/dist/web-mod-store.js.map +1 -1
  319. package/dist/web-prefs.d.ts +14 -0
  320. package/dist/web-prefs.js +24 -4
  321. package/dist/web-prefs.js.map +1 -1
  322. package/dist/web-preview.d.ts +6 -2
  323. package/dist/web-preview.js +4 -2
  324. package/dist/web-preview.js.map +1 -1
  325. package/dist/web-returning-device.d.ts +41 -0
  326. package/dist/web-returning-device.js +57 -5
  327. package/dist/web-returning-device.js.map +1 -1
  328. package/dist/web-session-authorization.d.ts +132 -0
  329. package/dist/web-session-authorization.js +193 -0
  330. package/dist/web-session-authorization.js.map +1 -0
  331. package/dist/web-session.d.ts +70 -10
  332. package/dist/web-session.js +137 -22
  333. package/dist/web-session.js.map +1 -1
  334. package/dist/web-settings-patch.d.ts +49 -2
  335. package/dist/web-settings-patch.js +145 -1
  336. package/dist/web-settings-patch.js.map +1 -1
  337. package/dist/web-settings-tools.d.ts +89 -0
  338. package/dist/web-settings-tools.js +270 -0
  339. package/dist/web-settings-tools.js.map +1 -0
  340. package/dist/web-shell-routes.d.ts +1 -1
  341. package/dist/web-shell-routes.js +1 -1
  342. package/dist/web-shell-routes.js.map +1 -1
  343. package/dist/web-theme-store.d.ts +39 -0
  344. package/dist/web-theme-store.js +122 -11
  345. package/dist/web-theme-store.js.map +1 -1
  346. package/dist/web-theme.d.ts +2 -0
  347. package/dist/web-theme.js +10 -1
  348. package/dist/web-theme.js.map +1 -1
  349. package/dist/workflow-templates/default.md +1 -1
  350. package/package.json +1 -1
  351. package/templates/systemd.service.ejs +8 -0
@@ -1,16 +1,15 @@
1
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";
2
+ import { allocateEffectFrames, effectEvent, effectPlaybackReason } from "./effect-policy.js";
3
+ import { createOpaqueEffectFrame } from "./effect-frame.js";
5
4
  import { createEffectGeometry, hostEffectRect } from "./effect-geometry.js";
6
- import { effectStockSnapshot, effectStockAllows } from "./effect-stock-snapshot.js";
5
+ import { effectStockSnapshot, effectStockAllows, effectStockRefusal } from "./effect-stock-snapshot.js";
7
6
  import { effectTrialStage, effectTrialStageIdentity, watchEffectTrialStage } from "./effect-trial-stage.js";
8
7
  import { EffectBudget } from "./effect-profiler.js";
9
8
 
10
9
  const registries = new WeakMap();
11
10
  const plain = o => !!o && (Object.getPrototypeOf(o) === Object.prototype || Object.getPrototypeOf(o) === null);
12
11
  const finite = v => typeof v === "number" && Number.isFinite(v);
13
- const FLAGS = ["safe", "publicLink", "reducedMotion", "saveData", "hidden", "enabled", "show", "previewReady", "optIn", "constrained", "modal"];
12
+ const FLAGS = ["safe", "publicLink", "reducedMotion", "hidden", "enabled", "show", "transportReady", "constrained", "modal"];
14
13
  const TYPES = ["state", "reply", "theme"];
15
14
  function unsafeSnapshotDescriptor(d) {
16
15
  if (!plain(d) || !plain(d.params ?? {})) return null;
@@ -41,9 +40,9 @@ function snapshotDescriptor(d) { try { return unsafeSnapshotDescriptor(d); } cat
41
40
  */
42
41
  export function createEffectRegistry({ window: host, origin, boot, load, safety, stockSnapshot, onChange = () => {}, onProfile = () => {}, onMount = () => {}, onRetire = () => {}, clock = () => host.performance.now() } = {}) {
43
42
  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;
43
+ let dead = false, blocked = null, revision = 0, last = null, lastView = null, frameSerial = 0, context = null, descriptors = [], raf = null, timer = null;
45
44
  const slots = new Map(), loads = new Set(), paused = new Map(), offs = [], observed = new Set(), failures = new Map(), results = new Map();
46
- let readingStock = false;
45
+ let readingStock = false, stockFailure = "stock_evidence_invalid", warned = null;
47
46
  let root, geometry, observer, resize, longTasks = null, longTasksSupported = false;
48
47
  const time = () => {
49
48
  let n; try { n = clock(); } catch { return null; }
@@ -65,14 +64,18 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
65
64
  finally { readingStages.delete(d.trialStage); }
66
65
  return !dead && !blocked && revision === before && stage?.root === d.element ? stage : null;
67
66
  };
68
- const permits = (d, proof) => d.trialStage ? !!localStage(d, proof) : effectStockAllows(proof, d);
67
+ const permits = (d, proof) => eligible(d) && (d.trialStage ? !!localStage(d, proof) : effectStockAllows(proof, d));
69
68
  const coverage = (d = null) => {
70
69
  if (dead || blocked || readingStock) return null;
71
70
  const before = revision; let proof = null; readingStock = true;
72
- try { proof = effectStockSnapshot(stockSnapshot(Object.freeze({ frameRoot: root }))); }
73
- catch { /* unknown is never permission */ }
71
+ let failure = "stock_provider_failed";
72
+ try { const raw = stockSnapshot(Object.freeze({ frameRoot: root })); proof = effectStockSnapshot(raw); failure = proof ? null : effectStockRefusal(raw); }
73
+ catch { /* unavailable stays denied, with a local diagnostic */ }
74
74
  finally { readingStock = false; }
75
75
  if (dead || blocked || before !== revision) return null;
76
+ stockFailure = failure;
77
+ if (failure !== warned) { warned = failure; if (failure) { try { host.console?.warn("[AgEnD effects] Playback blocked:", failure); } catch { /* display only */ } } }
78
+ if (dead || blocked || before !== revision) return null;
76
79
  return d === null || permits(d, proof) ? proof : null;
77
80
  };
78
81
  const position = (d, proof) => d.trialStage ? localStage(d, proof)?.rect ?? null : hostEffectRect(d.element);
@@ -91,6 +94,21 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
91
94
  const current = s => !dead && !blocked && slots.get(s.d.key) === s && s.live;
92
95
  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
96
  const receipt = (fn, ...args) => { try { return Reflect.apply(fn, safety, args); } catch { return null; } };
97
+ // #2055: the history's own read names a denial; any other answer stays unknown (and denied).
98
+ const SAFETY_REASON = Object.freeze({ __proto__: null, off: "safety_off", paused: "paused", interrupted: "interrupted" });
99
+ // Display only: the sticky history state per descriptor, which outranks a waiting reason (a queue, a dialog, typing)
100
+ // because it is what Resume must answer. Admission, caps, latches and safety policy never read it.
101
+ const named = new Map();
102
+ // The read can re-enter (an update from a callback): only a pass still current, for the same device, may write.
103
+ const nameSafety = (d, dev = device()) => {
104
+ const proposed = revision;
105
+ const reason = d.trialStage ? null : SAFETY_REASON[receipt(safety.status, d.id, dev)?.state];
106
+ if (dead || proposed !== revision || dev !== device()) return false;
107
+ if (reason) named.set(d.key, { identity: signature(d), reason }); else named.delete(d.key);
108
+ return true;
109
+ };
110
+ // Display only: the closed global gate a row is waiting on, when the host knows it (else the caller's unknown).
111
+ const gateReason = () => effectPlaybackReason(context);
94
112
  const clearMarker = (s, why) => {
95
113
  if (!s.token) return true;
96
114
  const token = s.token; s.token = null;
@@ -102,7 +120,9 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
102
120
  s.live = false;
103
121
  if (slots.get(s.d.key) === s) slots.delete(s.d.key);
104
122
  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 });
123
+ // A closed/unknown viewer gate revokes paint, not the effect identity. Fresh known gates
124
+ // may admit it again; resource failures and actual safety pauses retain their latches.
125
+ if (!["disposed", "superseded", "hidden", "human_resume", "geometry_changed", "gates_unknown"].includes(why)) failures.set(s.d.key, { identity: signature(s.d), reason: why });
106
126
  s.stageOff?.(); s.stageOff = null;
107
127
  s.frame?.remove(why); s.geometry?.remove();
108
128
  if (s.loadOff) s.loadOff();
@@ -110,6 +130,7 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
110
130
  paused.set(s.d.key, signature(s.d));
111
131
  if (receipt(safety.pause, { id: s.d.id, device: s.device, reason: why, metrics: s.budget?.snapshot().metrics ?? null }) !== true) blocked = "safety_unknown";
112
132
  }
133
+ if (count || why === "safety_revoked") nameSafety(s.d, s.device); // #2055: the third pause, or another tab's, says off now
113
134
  clearMarker(s, why);
114
135
  if (s.owner) { try { onRetire(s.owner, why); } catch { /* an observation cannot prevent physical cleanup */ } }
115
136
  if (blocked) for (const other of [...slots.values()]) retire(other, blocked);
@@ -120,18 +141,24 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
120
141
  retire(s, why);
121
142
  if (revision !== proposed || dead !== wasDead) return false;
122
143
  }
123
- if (calibration) { calibration.cancel(); calibration = null; }
124
144
  return true;
125
145
  }
126
146
  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";
147
+ function eligible(d = null) {
148
+ return gatesKnown() && !context.safe && !context.reducedMotion && context.enabled && context.show && context.transportReady
149
+ && (!d || !context.publicLink || d.region === "chat" && !d.trialStage);
150
+ }
151
+ const globalAllowed = (d = null) => eligible(d) && host.document.visibilityState === "visible";
129
152
  const signature = d => d.identity;
153
+ // A frame stopped because its gates closed is recorded as gates_unknown; when the host knows which gate, say it.
154
+ const shown = why => (why === "gates_unknown" && !eligible() && gateReason()) || why;
130
155
  function view() {
131
156
  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) };
157
+ const r = results.get(d.key) ?? { verdict: "off", reason: blocked ?? (eligible() ? stockFailure : gateReason()) ?? "runtime_unavailable", queuePos: null }, s = slots.get(d.key);
158
+ // #2055 one display precedence: a global latch, then the sticky safety history, then this page's own pause/failure.
159
+ const n = named.get(d.key), sticky = n && n.identity === signature(d) ? n.reason : null;
160
+ return { key: d.key, id: d.id, ...r, ...(sticky && r.verdict === "queued" ? { verdict: "off", queuePos: null } : {}), phase: s?.frame ? s.ready ? "running" : "starting" : s ? "loading" : null,
161
+ quality: s?.budget?.snapshot().quality ?? null, reason: blocked ?? sticky ?? ((paused.get(d.key) === signature(d)) ? "paused" : failures.get(d.key)?.identity === signature(d) ? shown(failures.get(d.key).reason) : s && context?.hidden ? "hidden" : r.reason) };
135
162
  });
136
163
  }
137
164
  function schedule() {
@@ -150,7 +177,7 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
150
177
  };
151
178
  function observe() {
152
179
  const nodes = host.document.querySelectorAll("[data-trust]");
153
- if (nodes.length > 128) throw Error("trust coverage unavailable");
180
+ if (nodes.length > 4096) throw Error("trust coverage unavailable");
154
181
  const wanted = new Set([host.document.documentElement, ...nodes]);
155
182
  for (const d of descriptors) if (d.element?.ownerDocument === host.document && d.element.isConnected === true) wanted.add(d.element);
156
183
  for (const n of observed) if (!wanted.has(n)) { resize.unobserve(n); observed.delete(n); }
@@ -163,9 +190,12 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
163
190
  if (time() === null) blocked = "clock_unavailable";
164
191
  const hidden = context?.hidden === true || host.document.visibilityState !== "visible";
165
192
  const stock = eligible() ? coverage() : null;
193
+ if (dead || proposal !== revision) return;
166
194
  if (blocked || !eligible() || !stock) {
167
195
  if (!stopAll(blocked ?? "gates_unknown")) return;
168
- results.clear(); tell(); return;
196
+ results.clear();
197
+ if (!blocked) for (const d of descriptors) if (!nameSafety(d) || dead || proposal !== revision) return;
198
+ tell(); return;
169
199
  }
170
200
  // Hidden frames already owned may survive for <=10 s, but no new frame or load is admitted while hidden.
171
201
  const ctx = { ...context, hidden: false };
@@ -176,6 +206,7 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
176
206
  if (dead || proposal !== revision) return;
177
207
  ds.push({ ...d, paused: !allowed || d.paused === true || paused.get(d.key) === signature(d) || failures.get(d.key)?.identity === signature(d) });
178
208
  }
209
+ for (const d of descriptors) if (!nameSafety(d) || dead || proposal !== revision) return;
179
210
  const allocation = allocateEffectFrames(ds, ctx); results.clear();
180
211
  for (const r of allocation) {
181
212
  const d = descriptors.find(d => d.key === r.key);
@@ -193,10 +224,9 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
193
224
  if (!chosen.has(s.d.key) || !d || signature(d) !== signature(s.d) || d.element !== s.d.element || s.device !== device()) retire(s, "superseded");
194
225
  if (dead || proposal !== revision) return;
195
226
  }
196
- if (chosen.size === 0) { if (calibration) { calibration.cancel(); calibration = null; } baseline = null; baselineDevice = null; tell(); return; }
227
+ if (chosen.size === 0) { tell(); return; }
197
228
  if (hidden) {
198
229
  hideGeometry();
199
- if (calibration) { calibration.cancel(); calibration = null; }
200
230
  for (const s of [...slots.values()]) {
201
231
  if (!s.frame) retire(s, "hidden");
202
232
  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"); }
@@ -209,19 +239,9 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
209
239
  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
240
  if (dead || proposal !== revision) return;
211
241
  }
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
- });
242
+ try { observe(); } catch {
243
+ if (dead || proposal !== revision || !stopAll("trust_geometry_unknown")) return;
244
+ for (const d of descriptors) results.set(d.key, { verdict: "off", reason: "trust_geometry_unknown", queuePos: null });
225
245
  tell(); return;
226
246
  }
227
247
  const max = context.constrained ? 1 : 3;
@@ -230,8 +250,14 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
230
250
  if (slots.size >= max || loads.size >= max) break;
231
251
  const permitted = receipt(safety.allowed, d.id, device());
232
252
  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;
253
+ if (permitted !== true) {
254
+ // #2055: say which denial it is. Only the safety history's own read names it; anything else stays unknown (still denied).
255
+ const st = receipt(safety.status, d.id, device());
256
+ if (proposal !== revision) return;
257
+ const why = SAFETY_REASON[st?.state] ?? "safety_unknown";
258
+ results.set(d.key, { verdict: "off", reason: why, queuePos: null }); continue;
259
+ }
260
+ if (slots.has(d.key) || slots.size >= max || loads.size >= max || !globalAllowed(d) || context.hidden || !coverage(d) || proposal !== revision || !descriptors.some(x => x.key === d.key && signature(x) === signature(d) && x.element === d.element)) break;
235
261
  const rect = position(d, coverage(d));
236
262
  if (dead || proposal !== revision) return;
237
263
  if (!rect || d.element.ownerDocument !== host.document) { results.set(d.key, { verdict: "off", reason: "geometry_unknown", queuePos: null }); continue; }
@@ -254,7 +280,7 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
254
280
  const acceptCompletion = () => {
255
281
  if (!current(s)) { clearMarker(s, "superseded"); return false; }
256
282
  if (!withinDeadline()) return false;
257
- if (request.controller.signal.aborted || !globalAllowed() || context.hidden || !coverage(d)) {
283
+ if (request.controller.signal.aborted || !globalAllowed(d) || context.hidden || !coverage(d)) {
258
284
  if (current(s)) retire(s, "admission_revoked"); else clearMarker(s, "superseded");
259
285
  return false;
260
286
  }
@@ -287,10 +313,10 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
287
313
  if (d.trialStage && !local || !current(s)) { if (current(s)) retire(s, "admission_revoked"); return; }
288
314
  if (local) fresh = local.rect;
289
315
  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,
316
+ s.budget = new EffectBudget({ device: s.device, clock: nativeClock, longTasksSupported });
317
+ const frame = createOpaqueEffectFrame({ window: host, root: local?.root ?? root, origin, boot, clock: nativeClock,
292
318
  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(); },
319
+ onReady() { if (!current(s)) return; if (!globalAllowed(d) || 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
320
  onRemove(why) { if (current(s)) { retire(s, why); tell(); } },
295
321
  });
296
322
  s.frame = frame;
@@ -316,7 +342,7 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
316
342
  schedule(); tell();
317
343
  }
318
344
  function publishEnv(s, proof = coverage(s.d)) {
319
- if (!s.ready || !current(s) || context.hidden || !globalAllowed() || !permits(s.d, proof)) return;
345
+ if (!s.ready || !current(s) || context.hidden || !globalAllowed(s.d) || !permits(s.d, proof)) return;
320
346
  const e = effectEvent("env", { width: s.rect.width, height: s.rect.height, dpr: dpr(s.device), quality: s.budget.snapshot().quality }, null, time());
321
347
  if (e) s.frame.send(e);
322
348
  }
@@ -339,12 +365,17 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
339
365
  if (changed) publishEnv(s, paint);
340
366
  if (s.ready) s.budget.frame();
341
367
  if (s.pointer) {
342
- const packet = s.pointer; s.pointer = null;
368
+ const packet = s.pointer;
343
369
  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)) {
370
+ if (n === null || n - packet.at > 100 || !permits(s.d, paint) || !pointerGate(paint) || !current(s) || !b.forward) {
371
+ if (s.pointer === packet) s.pointer = null;
372
+ } else if (s.lastPointer === null || n - s.lastPointer >= 1000 / b.pointerHz) {
373
+ if (s.pointer === packet) s.pointer = null;
345
374
  const event = effectEvent("pointer", packet.fields, s.d.instance, packet.at);
346
375
  if (event && s.frame.send(event)) s.lastPointer = n;
347
376
  }
377
+ // Keep the latest fresh sample until the next eligible frame. An early rAF must
378
+ // not consume a click just because the preceding move used this rate interval.
348
379
  }
349
380
  }
350
381
  if (dead || proposal !== revision) return;
@@ -401,7 +432,11 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
401
432
  if (!globalAllowed() || context.hidden || context.modal || !proof || proof.front !== true) return false;
402
433
  try {
403
434
  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'])");
435
+ // Native clicks on ordinary stock content focus its tabindex=-1 main ancestor.
436
+ // Recognize only that unique host root; unknown/editor focus remains a refusal.
437
+ const mains = focused?.tagName === "MAIN" ? host.document.querySelectorAll("#app > .shell > main#main.main[tabindex='-1']") : [];
438
+ const stockMain = focused?.isConnected === true && focused.ownerDocument === host.document && mains.length === 1 && mains[0] === focused;
439
+ return host.document.designMode !== "on" && !!focused && (["BODY", "BUTTON", "A"].includes(focused.tagName) || stockMain) && focused.isContentEditable === false && typeof focused.matches === "function" && !focused.matches("input,textarea,select,[contenteditable]:not([contenteditable='false'])");
405
440
  } catch { return false; }
406
441
  }
407
442
  function pointer(event) {
@@ -416,7 +451,11 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
416
451
  const fields = { kind, x: event.clientX - r.x, y: event.clientY - r.y, buttons: event.buttons };
417
452
  if (fields.x < 0 || fields.y < 0 || fields.x > r.width || fields.y > r.height) continue;
418
453
  if (kind === "wheel") { fields.dx = event.deltaX; fields.dy = event.deltaY; }
419
- if (effectEvent("pointer", fields, s.d.instance, at)) s.pointer = { fields, at };
454
+ if (effectEvent("pointer", fields, s.d.instance, at)) {
455
+ // One slot, with discrete input ahead of motion. A later move must not erase
456
+ // a waiting click; down/up/click may still coalesce to the latest discrete event.
457
+ if (kind !== "move" || !s.pointer || s.pointer.fields.kind === "move" || at - s.pointer.at > 100) s.pointer = { fields, at };
458
+ }
420
459
  }
421
460
  schedule();
422
461
  }
@@ -464,11 +503,11 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
464
503
  revalidateSafety,
465
504
  /** Called only by the host's explicit human Resume UI. A descriptor update never clears this latch. */
466
505
  resume(key) {
467
- if (dead || reentrantStock() || blocked && !blocked.startsWith("baseline_") || typeof key !== "string") return false;
506
+ if (dead || reentrantStock() || blocked || typeof key !== "string") return false;
468
507
  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
508
  revision++;
470
509
  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;
510
+ if (blocked) return false; paused.delete(key); failures.delete(key); reconcile(); return true;
472
511
  },
473
512
  /** Host-only content-free signal API. Raw pointer publishing is refused; native pointer metadata is coalesced here. */
474
513
  publish(type, fields, instance = null) {
@@ -484,7 +523,7 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
484
523
  return sent;
485
524
  },
486
525
  dispose() {
487
- if (dead) return; dead = true; revision++; serial++; stopAll("disposed");
526
+ if (dead) return; dead = true; revision++; stopAll("disposed");
488
527
  if (raf !== null) host.cancelAnimationFrame(raf); if (timer !== null) host.clearTimeout(timer);
489
528
  observer.disconnect(); resize.disconnect(); longTasks?.disconnect(); for (const off of offs) off();
490
529
  geometry.dispose(); root.remove(); observed.clear(); if (loads.size === 0 && registries.get(host) === api) registries.delete(host);
@@ -32,3 +32,12 @@ export function effectStockAllows(snapshot, { layer, by } = {}) {
32
32
  const proof = effectStockSnapshot(snapshot);
33
33
  return !!proof && (layer === "behind" || layer === "front" && proof.front && (by === "human" || proof.agentFront));
34
34
  }
35
+
36
+ /** Display only. Refused provider metadata never becomes paint evidence or authority. */
37
+ export function effectStockRefusal(value) {
38
+ try {
39
+ if (!plain(value)) return "stock_evidence_invalid";
40
+ const d = Object.getOwnPropertyDescriptor(value, "reason");
41
+ return d && Object.hasOwn(d, "value") && typeof d.value === "string" && /^[a-z][a-z0-9_]{0,79}$/.test(d.value) ? d.value : "stock_evidence_invalid";
42
+ } catch { return "stock_evidence_invalid"; }
43
+ }
@@ -11,7 +11,7 @@ const SPECS = [
11
11
  ["confirmation", ".shell > .pending-stack, .shell .p-settings > .panel-body > .col > .pending-stack.inline"], ["preview", ".shell .chat-split > .pv-panel, .shell .chat-split > .pv-divider"],
12
12
  ["preview-menu", ".shell .thread details.pv-menu"], ["palette", ".shell .dock .cmd-palette"],
13
13
  ["drop", ".shell .p-chat > .drop-overlay"],
14
- ["thread-control", ".shell .p-chat .jump-latest, .shell .thread .msg-tools, .shell .thread .st-more, .shell .thread .cb-head, .shell .thread .cb-more, .shell .thread .tc-head, .shell .thread .tc-more, .shell .thread .pv-head, .shell .thread .pv-note, .shell .thread .pv-banner, .shell .thread .effect-trial-offer, .shell .thread .click-chip"],
14
+ ["thread-control", ".shell .chat-main > .amb-note, .shell .p-chat .jump-latest, .shell .thread .msg-tools, .shell .thread .st-more, .shell .thread .cb-head, .shell .thread .cb-more, .shell .thread .tc-head, .shell .thread .tc-more, .shell .thread .pv-head, .shell .thread .pv-note, .shell .thread .pv-banner, .shell .thread .effect-trial-offer, .shell .thread .click-chip"],
15
15
  ["link", ".shell .thread .md a, .shell .thread .st-text > a.st-link, .shell .dock .prompts .txt > a.st-link"], ["attachment", ".shell .thread .atts"], ["media", ".shell .thread .att-media"],
16
16
  ["panel-navigation", ".shell .p-fleet > .seg, .shell .p-settings > .seg"],
17
17
  ["panel-content", ".shell .p-fleet > .panel-body, .shell .p-needs > .panel-body, .shell .p-settings > .panel-body, .shell .p-log > .panel-body, .shell .p-details > .panel-body, .shell .p-view > .panel-body, .shell .p-view > .v-term, .shell .p-view > .v-id, .shell .p-view > .v-card"],
@@ -20,7 +20,7 @@ const SPECS = [
20
20
  ];
21
21
  export const STOCK_TRUST_INVENTORY = Object.freeze(SPECS.map(([kind, selector]) => Object.freeze({ kind, selector })));
22
22
  const STRUCTURAL = ["html", "body", "#app", "#app > .shell", ".shell > .main", ".main > .p-chat", ".p-chat > .chat-split", ".chat-split > .chat-main"];
23
- const CONTENT = [".main > .conn", ".p-chat > .panel-head", ".p-chat > .chat-card", ".p-chat > .aw-chat_header", ".chat-main > .scroller", ".chat-main > .dock", ".sidebar > .side-head", ".sidebar > .side-nav", ".sidebar > .side-section", ".sidebar > .side-widget", ".sidebar > .side-foot", ".u-rail > .u-rail-head", ".u-rail > .u-rail-body"];
23
+ const CONTENT = [".main > .conn", ".p-chat > .panel-head", ".p-chat > .chat-card", ".p-chat > .aw-chat_header", ".chat-main > .amb-note", ".chat-main > .scroller", ".chat-main > .dock", ".sidebar > .side-head", ".sidebar > .side-nav", ".sidebar > .side-section", ".sidebar > .side-widget", ".sidebar > .side-foot", ".u-rail > .u-rail-head", ".u-rail > .u-rail-body"];
24
24
  // Closed producer/topology inventory. Bare Outlet loading/error panels never become an effect surface.
25
25
  const PANELS = Object.freeze({
26
26
  fleet: { required: [".panel-head", ".seg", ".panel-body"], optional: [] },
@@ -95,14 +95,58 @@ function outset(style) {
95
95
  }
96
96
  return pad <= 256 ? Math.ceil(pad) : null;
97
97
  }
98
+ /**
99
+ * #2086: the identity card's banner fill (a blurred copy beside a centred banner) — the one filter stock admits. Only
100
+ * this exact value, on exactly `.id-banner-box > img.id-banner-fill`, absolutely positioned in that box (so the box is
101
+ * its containing block — #2089 review: a fixed fill is the viewport's, and no box clips it), and the box positioned and
102
+ * clipping on both axes; its paint is then that box's rectangle (the blur and its scale never leave it). The box must
103
+ * also be a real block box that the browser reports as the fill's offset parent (#2089 review r3: `display: contents`
104
+ * keeps the computed position and overflow but generates no box, so an outer ancestor would place the fill, and an inline
105
+ * box clips differently). Anything else with a filter refuses.
106
+ */
107
+ export const STOCK_BANNER_FILL_FILTER = "blur(24px) saturate(0.75) brightness(0.7)";
108
+ function stockBannerFill(host, el) {
109
+ const box = el.parentElement;
110
+ if (typeof el.matches !== "function" || !el.matches(".id-banner-box > img.id-banner-fill") || !box) return null;
111
+ const s = host.getComputedStyle(el), b = host.getComputedStyle(box);
112
+ if (s.filter !== STOCK_BANNER_FILL_FILTER || !none(s.backdropFilter) || !none(b.filter) || !none(b.backdropFilter)) return null;
113
+ if (!["hidden", "clip"].includes(b.overflowX) || !["hidden", "clip"].includes(b.overflowY)) return null;
114
+ if (s.position !== "absolute" || !["relative", "absolute"].includes(b.position)) return null;
115
+ if (b.display !== "block" || el.offsetParent !== box) return null;
116
+ return rect(box);
117
+ }
118
+ function zeroClipMargin(host, style) {
119
+ const margin = style.overflowClipMargin ?? style.getPropertyValue?.("overflow-clip-margin");
120
+ // WebKit versions without this property still implement paint containment. An absent
121
+ // computed value is usable only when the engine explicitly reports it unsupported;
122
+ // unknown capability or a supported but unreadable/expanded margin cannot prove a box.
123
+ return margin === "0px" || [undefined, ""].includes(margin)
124
+ && host.CSS?.supports?.("overflow-clip-margin", "1px") === false;
125
+ }
98
126
  function protectedPaint(host, root, budget) {
127
+ // These two stock toolbar boxes physically clip ALL descendants, including fixed-position
128
+ // children. Their entire box is a hole; descendant ink cannot escape it. Inline, fragmented,
129
+ // expanded-clip or CSS-overridden boxes take the ordinary bounded walk instead.
130
+ const s = host.getComputedStyle(root);
131
+ if (root.matches?.(".shell .thread .msg-tools, .shell .thread .codeblock > .cb-head") && s.contain === "paint"
132
+ && s.display === "flex" && s.overflowX === "clip" && s.overflowY === "clip" && zeroClipMargin(host, s)
133
+ && none(s.filter) && none(s.backdropFilter) && s.borderImageSource === "none" && [undefined, "", "visible"].includes(s.contentVisibility) && root.getClientRects().length === 1) {
134
+ if (++budget.n > 4096) { budget.exceeded = true; return null; }
135
+ const r = rect(root), pad = outset(s);
136
+ // Containment clips descendants, not the root's own ink. Do not infer a world-space
137
+ // outset from local CSS lengths under a potentially transformed content ancestor.
138
+ if (!r || pad !== 0) return null;
139
+ return r;
140
+ }
99
141
  const nodes = [root, ...root.querySelectorAll("*")]; budget.n += nodes.length;
100
- if (budget.n > 4096) return null;
142
+ if (budget.n > 4096) { budget.exceeded = true; return null; }
101
143
  let union = null;
102
144
  for (const el of nodes) {
103
145
  if (!visible(host, el)) continue;
104
- const style = host.getComputedStyle(el), r = rect(el), pad = outset(style);
105
- if (!r || pad === null || !none(style.filter) || !none(style.backdropFilter)) return null;
146
+ const style = host.getComputedStyle(el), pad = outset(style);
147
+ if (style.borderImageSource !== undefined && style.borderImageSource !== "none" || ![undefined, "", "visible"].includes(style.contentVisibility)) return null;
148
+ const r = none(style.filter) ? rect(el) : stockBannerFill(host, el);
149
+ if (!r || pad === null || !none(style.backdropFilter)) return null;
106
150
  const a = { x: r.x - pad, y: r.y - pad, width: r.width + pad * 2, height: r.height + pad * 2 };
107
151
  if (!union) union = a;
108
152
  else { const x = Math.min(union.x, a.x), y = Math.min(union.y, a.y); union = { x, y, width: Math.max(union.x + union.width, a.x + a.width) - x, height: Math.max(union.y + union.height, a.y + a.height) - y }; }
@@ -144,22 +188,30 @@ export function stockEffectTrust(host, { frameRoot = null, ownerCss = "unknown"
144
188
  if (s.position !== "relative" || s.zIndex !== "1" || !neutral(s)) return fail("stock_order_unknown");
145
189
  }
146
190
  const marked = [...doc.querySelectorAll("[data-trust]")], expected = new Set(), rects = [], budget = { n: 0 };
147
- if (marked.length > 128) return fail("stock_trust_unknown");
191
+ if (marked.length > 4096) return fail("stock_marker_limit");
148
192
  for (const { kind, selector } of STOCK_TRUST_INVENTORY) for (const el of doc.querySelectorAll(selector)) {
149
193
  if (el.ownerDocument !== doc || el.isConnected !== true || el.getAttribute("data-trust") !== kind) return fail("stock_trust_unknown");
150
194
  expected.add(el);
151
195
  }
152
196
  if (marked.some(el => !expected.has(el)) || surface.kind === "chat" && !chat.querySelector('.dock[data-trust="composer"]') || !surface.panel.querySelector('.panel-head[data-trust="header"]')) return fail("stock_trust_unknown");
153
197
  for (const el of marked) {
154
- const bounds = protectedPaint(host, el, budget); if (!bounds) return fail("stock_paint_unknown");
155
- if (bounds.width && bounds.height) rects.push(bounds);
198
+ const bounds = protectedPaint(host, el, budget); if (!bounds) return fail(budget.exceeded ? "stock_paint_limit" : "stock_paint_unknown");
199
+ // Off-screen history remains inventoried and paint-checked; it need not consume a
200
+ // viewport hole. Include the clip algorithm's extra 8px protection margin.
201
+ if (bounds.width && bounds.height && (!finite(host.innerHeight) || bounds.x + bounds.width + 8 >= 0 && bounds.y + bounds.height + 8 >= 0 && bounds.x - 8 <= host.innerWidth && bounds.y - 8 <= host.innerHeight)) rects.push(bounds);
202
+ }
203
+ // Keep the existing 128-hole transport bound. Conservative union may hide extra
204
+ // effect pixels; it can never reveal protected pixels, even for a dense viewport.
205
+ if (rects.length > 128) {
206
+ const rest = rects.splice(127), x = Math.min(...rest.map(r => r.x)), y = Math.min(...rest.map(r => r.y));
207
+ rects.push({ x, y, width: Math.max(...rest.map(r => r.x + r.width)) - x, height: Math.max(...rest.map(r => r.y + r.height)) - y });
156
208
  }
157
209
  if (doc.querySelector(".agend-session-ended")) return fail("session_ended");
158
210
  let barrier = [...doc.querySelectorAll(BARRIERS)].some(el => visible(host, el));
159
211
  if (host.innerWidth < 900 && [...doc.querySelectorAll(".pv-panel")].some(el => visible(host, el))) barrier = true;
160
- const active = doc.activeElement;
161
212
  if (doc.fullscreenElement || doc.pictureInPictureElement) barrier = true;
162
- if (active && active !== doc.body && active !== doc.documentElement && (active.matches("input,select,textarea,[contenteditable]") || active.isContentEditable)) barrier = true;
213
+ // Focus alone does not change protected paint bounds. Composer/controls stay clipped; editable focus
214
+ // separately suppresses pointer delivery in the registry, and keyboard events never enter a Worker.
163
215
  return { ok: true, rects, front: !barrier, reason: barrier ? "stock_overlay" : "stock", agentFront: !barrier && ownerCss === "off", agentReason: barrier ? "stock_overlay" : ownerCss === "off" ? "stock" : "owner_css", settingsHref: "/settings/appearance" };
164
216
  } catch { return fail("stock_trust_unknown"); }
165
217
  }
@@ -0,0 +1,62 @@
1
+ /** #2039: fixed host-only closed shadow shell. Owner CSS has no exported parts or slots into it. */
2
+ const roots = new WeakMap();
3
+ const clear = root => { while (root.firstChild) root.removeChild(root.firstChild); };
4
+ function sheetOf(host, record) {
5
+ try {
6
+ const link = record.link, sheet = link?.sheet;
7
+ return record.ready && !record.dead && roots.get(record.panel)?.current === record && record.root.mode === "closed" && record.root.host === record.panel
8
+ && link.getRootNode() === record.root && link.isConnected && link.disabled === false && sheet?.ownerNode === link && sheet.disabled === false
9
+ && sheet.href === new URL("/assets/effect-trial.css", host.location.origin).href && typeof sheet.insertRule === "function" && typeof sheet.deleteRule === "function" && sheet.cssRules ? sheet : null;
10
+ } catch { return null; }
11
+ }
12
+ /** Reflect only a shell issued here, after its fixed same-origin stylesheet loaded. Never a serialized capability. */
13
+ export function effectTrialShadow(host, panel) {
14
+ const record = roots.get(panel)?.current;
15
+ return record && sheetOf(host, record) === record.proof?.sheet ? record.proof : null;
16
+ }
17
+ export function createEffectTrialShadow({ window: host, panel, title, labels, onStop, onClose, onReady, onError } = {}) {
18
+ if (!host?.document || panel?.ownerDocument !== host.document || typeof panel.attachShadow !== "function" || typeof onReady !== "function" || typeof onError !== "function") return null;
19
+ let slot = roots.get(panel), cleanup = null;
20
+ const clock = () => (host.performance ?? globalThis.performance).now(), later = host.setTimeout?.bind(host) ?? globalThis.setTimeout, cancel = host.clearTimeout?.bind(host) ?? globalThis.clearTimeout;
21
+ try {
22
+ if (slot?.current && !slot.current.dead) return null;
23
+ if (!slot) { slot = { root: panel.attachShadow({ mode: "closed" }), current: null }; roots.set(panel, slot); }
24
+ const root = slot.root; clear(root);
25
+ const doc = host.document, element = (tag, cls) => { const el = doc.createElement(tag); el.className = cls; return el; };
26
+ const link = element("link", "effect-trial-style"); link.setAttribute("rel", "stylesheet"); link.setAttribute("href", "/assets/effect-trial.css");
27
+ const head = element("header", "effect-trial-head"), label = element("strong", "effect-trial-label"), name = element("span", "effect-trial-title"), close = element("button", "effect-trial-close"), controls = element("div", "effect-trial-controls"), stop = element("button", "effect-trial-stop"), body = element("div", "effect-trial-body"), stage = element("div", "effect-trial-stage"), note = element("p", "effect-trial-note");
28
+ close.setAttribute("type", "button"); stop.setAttribute("type", "button"); stage.setAttribute("aria-hidden", "true");
29
+ head.append(label, name, close); controls.append(stop); body.append(stage);
30
+ const nodes = [head, controls, body, note]; for (const el of nodes) el.setAttribute("hidden", "");
31
+ const began = clock(); if (!Number.isFinite(began) || began < 0) return null;
32
+ let timer = null;
33
+ const record = { panel, root, link, stage, head, controls, body, note, close, stop, ready: false, dead: false, proof: null }; slot.current = record;
34
+ const text = (nextTitle, nextLabels) => {
35
+ if (record.dead || !nextLabels) return;
36
+ label.textContent = nextLabels.label; name.textContent = typeof nextTitle === "string" ? nextTitle : "";
37
+ close.textContent = nextLabels.close; stop.textContent = nextLabels.stop; note.textContent = nextLabels.note;
38
+ panel.setAttribute("data-effect-trial-theme", doc.documentElement.dataset.theme === "dark" ? "dark" : "light");
39
+ };
40
+ const failed = () => { if (!record.dead) { dispose(); try { onError("stage_unavailable"); } catch { /* cleanup already owns failure */ } } };
41
+ const loaded = () => {
42
+ if (record.dead || record.ready || slot.current !== record) return;
43
+ const at = clock(); if (!Number.isFinite(at) || at < began || at - began >= 10000) { failed(); return; }
44
+ record.ready = true; const sheet = sheetOf(host, record);
45
+ if (!sheet) { failed(); return; }
46
+ cancel(timer); timer = null;
47
+ record.proof = Object.freeze({ panel, root, link, sheet, stage, head, controls, body, note, close, stop });
48
+ for (const el of nodes) el.removeAttribute("hidden");
49
+ try { onReady(record.proof); } catch { failed(); }
50
+ };
51
+ const stopClick = () => { if (!record.dead) onStop?.(); }, closeClick = () => { if (!record.dead) onClose?.(); };
52
+ function dispose() {
53
+ if (record.dead) return; record.dead = true; cancel(timer); timer = null;
54
+ link.removeEventListener("load", loaded); link.removeEventListener("error", failed); stop.removeEventListener("click", stopClick); close.removeEventListener("click", closeClick);
55
+ if (slot.current === record) { slot.current = null; clear(root); }
56
+ }
57
+ cleanup = dispose;
58
+ link.addEventListener("load", loaded); link.addEventListener("error", failed); stop.addEventListener("click", stopClick); close.addEventListener("click", closeClick);
59
+ text(title, labels); timer = later(failed, 10000); root.append(link, ...nodes);
60
+ return Object.freeze({ dispose, text });
61
+ } catch { cleanup?.(); return null; }
62
+ }
@@ -64,7 +64,7 @@ export function createEffectTrialSource({ window: host, current, ownerCurrent, s
64
64
  const lease = { identity, reply, stage, owner, current: x.current, onStop: x.onStop, startedAt: x.startedAt, until, dead: false, checking: false, pending: new Set(), timer: null, off: null, binding: null };
65
65
  lease.binding = Object.freeze({ kind: "trial", key: identity.key, id: identity.id, source: lease, generation: identity.generation });
66
66
  active = lease; used.add(stage.receipt);
67
- lease.off = watchEffectTrialStage(stage.receipt, () => stop(lease, "stage_ended"));
67
+ lease.off = watchEffectTrialStage(stage.receipt, reason => stop(lease, typeof reason === "string" ? reason : "stage_ended"));
68
68
  if (typeof lease.off !== "function" || constrainEffectTrialStage(stage.receipt, () => checked(lease)) !== true || !checked(lease)) { stop(lease, "stage_unavailable"); return null; }
69
69
  try { arm(lease); } catch { stop(lease, "trial_unavailable"); return null; }
70
70
  if (!checked(lease)) return null;