@songsid/agend 2.3.3-beta.1 → 2.3.3-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (355) hide show
  1. package/dist/agent-cli.js +13 -112
  2. package/dist/agent-cli.js.map +1 -1
  3. package/dist/agent-endpoint.d.ts +0 -1
  4. package/dist/agent-endpoint.js +1 -9
  5. package/dist/agent-endpoint.js.map +1 -1
  6. package/dist/agent-whats-new.d.ts +3 -6
  7. package/dist/agent-whats-new.js +7 -18
  8. package/dist/agent-whats-new.js.map +1 -1
  9. package/dist/auth-api.d.ts +0 -7
  10. package/dist/auth-api.js +1 -15
  11. package/dist/auth-api.js.map +1 -1
  12. package/dist/authority/authority-state.js +1 -1
  13. package/dist/authority/authority-state.js.map +1 -1
  14. package/dist/authority/web-authority.d.ts +9 -4
  15. package/dist/authority/web-authority.js +10 -5
  16. package/dist/authority/web-authority.js.map +1 -1
  17. package/dist/backend/antigravity.js +6 -1
  18. package/dist/backend/antigravity.js.map +1 -1
  19. package/dist/backend/claude-code.d.ts +7 -0
  20. package/dist/backend/claude-code.js +34 -28
  21. package/dist/backend/claude-code.js.map +1 -1
  22. package/dist/backend/claude-transcript-errors.d.ts +18 -0
  23. package/dist/backend/claude-transcript-errors.js +19 -0
  24. package/dist/backend/claude-transcript-errors.js.map +1 -0
  25. package/dist/backend/codex-session-lookup.d.ts +16 -3
  26. package/dist/backend/codex-session-lookup.js +45 -11
  27. package/dist/backend/codex-session-lookup.js.map +1 -1
  28. package/dist/backend/codex.d.ts +16 -0
  29. package/dist/backend/codex.js +184 -35
  30. package/dist/backend/codex.js.map +1 -1
  31. package/dist/backend/grok.js +58 -12
  32. package/dist/backend/grok.js.map +1 -1
  33. package/dist/backend/kiro.js +2 -0
  34. package/dist/backend/kiro.js.map +1 -1
  35. package/dist/backend/muse.js +2 -1
  36. package/dist/backend/muse.js.map +1 -1
  37. package/dist/backend/types.d.ts +46 -0
  38. package/dist/backend/types.js.map +1 -1
  39. package/dist/bundled-skills.d.ts +4 -6
  40. package/dist/bundled-skills.js +10 -14
  41. package/dist/bundled-skills.js.map +1 -1
  42. package/dist/channel/adapters/discord.js +60 -17
  43. package/dist/channel/adapters/discord.js.map +1 -1
  44. package/dist/channel/mcp-server.js +5 -5
  45. package/dist/channel/mcp-server.js.map +1 -1
  46. package/dist/channel/mcp-tools.d.ts +1 -3
  47. package/dist/channel/mcp-tools.js +4 -58
  48. package/dist/channel/mcp-tools.js.map +1 -1
  49. package/dist/classic-channel-manager.d.ts +4 -1
  50. package/dist/classic-channel-manager.js +21 -7
  51. package/dist/classic-channel-manager.js.map +1 -1
  52. package/dist/cli.js +21 -4
  53. package/dist/cli.js.map +1 -1
  54. package/dist/config-history.d.ts +50 -0
  55. package/dist/config-history.js +263 -0
  56. package/dist/config-history.js.map +1 -0
  57. package/dist/config-validator.js +7 -7
  58. package/dist/config-validator.js.map +1 -1
  59. package/dist/config.d.ts +3 -0
  60. package/dist/config.js +11 -4
  61. package/dist/config.js.map +1 -1
  62. package/dist/daemon.d.ts +73 -3
  63. package/dist/daemon.js +334 -121
  64. package/dist/daemon.js.map +1 -1
  65. package/dist/daily-summary.d.ts +8 -1
  66. package/dist/daily-summary.js +8 -5
  67. package/dist/daily-summary.js.map +1 -1
  68. package/dist/dashboard-live.d.ts +1 -1
  69. package/dist/dashboard-live.js +3 -3
  70. package/dist/dashboard-live.js.map +1 -1
  71. package/dist/dashboard-permissions.d.ts +1 -1
  72. package/dist/dashboard-permissions.js +1 -3
  73. package/dist/dashboard-permissions.js.map +1 -1
  74. package/dist/delivery-outbox.d.ts +4 -0
  75. package/dist/delivery-outbox.js +12 -2
  76. package/dist/delivery-outbox.js.map +1 -1
  77. package/dist/doctor.d.ts +4 -0
  78. package/dist/doctor.js +16 -1
  79. package/dist/doctor.js.map +1 -1
  80. package/dist/fleet-level-config.js +1 -1
  81. package/dist/fleet-level-config.js.map +1 -1
  82. package/dist/fleet-manager.d.ts +70 -54
  83. package/dist/fleet-manager.js +257 -322
  84. package/dist/fleet-manager.js.map +1 -1
  85. package/dist/fleet-process-handlers.d.ts +5 -0
  86. package/dist/fleet-process-handlers.js +21 -0
  87. package/dist/fleet-process-handlers.js.map +1 -1
  88. package/dist/fleet-service-owner.d.ts +11 -0
  89. package/dist/fleet-service-owner.js +3 -0
  90. package/dist/fleet-service-owner.js.map +1 -0
  91. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +11 -1
  92. package/dist/general-knowledge/skills/session-management/SKILL.md +6 -0
  93. package/dist/general-knowledge/skills/web-appearance/SKILL.md +13 -204
  94. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +29 -67
  95. package/dist/instance-lifecycle.d.ts +19 -0
  96. package/dist/instance-lifecycle.js +52 -10
  97. package/dist/instance-lifecycle.js.map +1 -1
  98. package/dist/locale.js +92 -42
  99. package/dist/locale.js.map +1 -1
  100. package/dist/needs-you-hub.d.ts +1 -0
  101. package/dist/needs-you-hub.js +6 -3
  102. package/dist/needs-you-hub.js.map +1 -1
  103. package/dist/needs-you-live.d.ts +3 -1
  104. package/dist/needs-you-live.js +24 -2
  105. package/dist/needs-you-live.js.map +1 -1
  106. package/dist/needs-you.d.ts +13 -2
  107. package/dist/needs-you.js +11 -1
  108. package/dist/needs-you.js.map +1 -1
  109. package/dist/notify-socket.d.ts +10 -0
  110. package/dist/notify-socket.js +45 -0
  111. package/dist/notify-socket.js.map +1 -0
  112. package/dist/outbound-handlers.js +19 -7
  113. package/dist/outbound-handlers.js.map +1 -1
  114. package/dist/outbound-schemas.d.ts +0 -62
  115. package/dist/outbound-schemas.js +3 -42
  116. package/dist/outbound-schemas.js.map +1 -1
  117. package/dist/parked-notices.d.ts +87 -0
  118. package/dist/parked-notices.js +194 -0
  119. package/dist/parked-notices.js.map +1 -0
  120. package/dist/public-link-progress.d.ts +5 -2
  121. package/dist/public-link-progress.js +4 -3
  122. package/dist/public-link-progress.js.map +1 -1
  123. package/dist/public-web-gateway.js +0 -9
  124. package/dist/public-web-gateway.js.map +1 -1
  125. package/dist/public-web-link.d.ts +12 -0
  126. package/dist/public-web-link.js +29 -5
  127. package/dist/public-web-link.js.map +1 -1
  128. package/dist/quota-notice.d.ts +23 -0
  129. package/dist/quota-notice.js +22 -0
  130. package/dist/quota-notice.js.map +1 -0
  131. package/dist/scheduler/task-board.d.ts +0 -13
  132. package/dist/scheduler/task-board.js +0 -14
  133. package/dist/scheduler/task-board.js.map +1 -1
  134. package/dist/sd-notify.d.ts +10 -1
  135. package/dist/sd-notify.js +27 -2
  136. package/dist/sd-notify.js.map +1 -1
  137. package/dist/search/search-docs.d.ts +1 -1
  138. package/dist/search/search-docs.js +2 -2
  139. package/dist/search/search-docs.js.map +1 -1
  140. package/dist/settings-api.d.ts +16 -0
  141. package/dist/settings-api.js +55 -2
  142. package/dist/settings-api.js.map +1 -1
  143. package/dist/settings-change.js +3 -1
  144. package/dist/settings-change.js.map +1 -1
  145. package/dist/settings-confirmation.d.ts +17 -3
  146. package/dist/settings-confirmation.js +8 -5
  147. package/dist/settings-confirmation.js.map +1 -1
  148. package/dist/settings-http-confirmation.js +31 -1
  149. package/dist/settings-http-confirmation.js.map +1 -1
  150. package/dist/setup-wizard.d.ts +3 -0
  151. package/dist/setup-wizard.js +4 -2
  152. package/dist/setup-wizard.js.map +1 -1
  153. package/dist/task-board-waker.d.ts +4 -1
  154. package/dist/task-board-waker.js +13 -2
  155. package/dist/task-board-waker.js.map +1 -1
  156. package/dist/tool-permissions.js +3 -10
  157. package/dist/tool-permissions.js.map +1 -1
  158. package/dist/topic-commands.d.ts +27 -7
  159. package/dist/topic-commands.js +95 -36
  160. package/dist/topic-commands.js.map +1 -1
  161. package/dist/transcript-monitor.d.ts +7 -0
  162. package/dist/transcript-monitor.js +48 -3
  163. package/dist/transcript-monitor.js.map +1 -1
  164. package/dist/tunnel/cloudflared.d.ts +5 -0
  165. package/dist/tunnel/cloudflared.js +32 -1
  166. package/dist/tunnel/cloudflared.js.map +1 -1
  167. package/dist/tunnel/named-diagnostics.d.ts +2 -1
  168. package/dist/tunnel/named-diagnostics.js +4 -1
  169. package/dist/tunnel/named-diagnostics.js.map +1 -1
  170. package/dist/tunnel/named-link-test.d.ts +90 -0
  171. package/dist/tunnel/named-link-test.js +270 -0
  172. package/dist/tunnel/named-link-test.js.map +1 -0
  173. package/dist/tunnel/purpose-lane.d.ts +3 -1
  174. package/dist/tunnel/purpose-lane.js +2 -0
  175. package/dist/tunnel/purpose-lane.js.map +1 -1
  176. package/dist/types.d.ts +8 -6
  177. package/dist/ui/chat-ambient.js +4 -4
  178. package/dist/ui/chat-commands.js +3 -2
  179. package/dist/ui/chat-store.js +4 -1
  180. package/dist/ui/chat-strings.js +12 -46
  181. package/dist/ui/chat-thread.js +51 -59
  182. package/dist/ui/fleet-office-skin.js +37 -14
  183. package/dist/ui/fleet-office.js +31 -7
  184. package/dist/ui/html-attachment.js +22 -8
  185. package/dist/ui/panel-chat.js +21 -50
  186. package/dist/ui/panel-needs.js +11 -9
  187. package/dist/ui/panel-settings.js +5 -3
  188. package/dist/ui/preview-panel.js +12 -21
  189. package/dist/ui/preview.js +10 -3
  190. package/dist/ui/search-dialog.js +30 -14
  191. package/dist/ui/settings-appearance.js +1 -3
  192. package/dist/ui/settings-confirm.js +18 -0
  193. package/dist/ui/settings-guests.js +13 -4
  194. package/dist/ui/settings-learn.js +1 -1
  195. package/dist/ui/settings-mods.js +5 -12
  196. package/dist/ui/settings-named-test.js +144 -0
  197. package/dist/ui/settings-registry.js +0 -1
  198. package/dist/ui/settings-strings.js +14 -14
  199. package/dist/ui/settings-web-authority.js +4 -3
  200. package/dist/ui/shared/app-i18n.js +15 -4
  201. package/dist/ui/shared/app-needs.js +5 -1
  202. package/dist/ui/shared/app-prefs.js +17 -17
  203. package/dist/ui/shared/app-shell.js +47 -9
  204. package/dist/ui/shared/app-store.js +6 -2
  205. package/dist/ui/shared/app.css +33 -39
  206. package/dist/ui/shared/app.js +5 -4
  207. package/dist/ui/shared/instance-nav.js +9 -9
  208. package/dist/ui/shared/instance-switch.js +8 -2
  209. package/dist/ui/shared/panel-view.js +22 -8
  210. package/dist/ui/shared/view-strings.js +6 -0
  211. package/dist/ui/shared/web-authorize.js +35 -13
  212. package/dist/usage/usage-api.d.ts +3 -0
  213. package/dist/usage/usage-api.js +5 -1
  214. package/dist/usage/usage-api.js.map +1 -1
  215. package/dist/view-api.d.ts +2 -0
  216. package/dist/view-api.js +64 -0
  217. package/dist/view-api.js.map +1 -1
  218. package/dist/web-api.d.ts +9 -17
  219. package/dist/web-api.js +257 -371
  220. package/dist/web-api.js.map +1 -1
  221. package/dist/web-appearance-tools.d.ts +0 -2
  222. package/dist/web-appearance-tools.js +2 -5
  223. package/dist/web-appearance-tools.js.map +1 -1
  224. package/dist/web-appearance-view.d.ts +0 -6
  225. package/dist/web-appearance-view.js +0 -1
  226. package/dist/web-appearance-view.js.map +1 -1
  227. package/dist/web-appearance.d.ts +14 -7
  228. package/dist/web-appearance.js +5 -6
  229. package/dist/web-appearance.js.map +1 -1
  230. package/dist/web-asset-parts.d.ts +1 -17
  231. package/dist/web-asset-parts.js +1 -15
  232. package/dist/web-asset-parts.js.map +1 -1
  233. package/dist/web-auth.js +1 -7
  234. package/dist/web-auth.js.map +1 -1
  235. package/dist/web-chat-history.d.ts +3 -7
  236. package/dist/web-chat-history.js +10 -17
  237. package/dist/web-chat-history.js.map +1 -1
  238. package/dist/web-commands.d.ts +9 -1
  239. package/dist/web-commands.js +13 -0
  240. package/dist/web-commands.js.map +1 -1
  241. package/dist/web-experimental.d.ts +2 -11
  242. package/dist/web-experimental.js +8 -36
  243. package/dist/web-experimental.js.map +1 -1
  244. package/dist/web-guest-access.d.ts +40 -2
  245. package/dist/web-guest-access.js +50 -14
  246. package/dist/web-guest-access.js.map +1 -1
  247. package/dist/web-mod-bundle.d.ts +2 -45
  248. package/dist/web-mod-bundle.js +15 -63
  249. package/dist/web-mod-bundle.js.map +1 -1
  250. package/dist/web-mod-store.d.ts +3 -48
  251. package/dist/web-mod-store.js +30 -97
  252. package/dist/web-mod-store.js.map +1 -1
  253. package/dist/web-prefs.d.ts +13 -0
  254. package/dist/web-prefs.js +38 -0
  255. package/dist/web-prefs.js.map +1 -1
  256. package/dist/web-preview.d.ts +6 -13
  257. package/dist/web-preview.js +12 -23
  258. package/dist/web-preview.js.map +1 -1
  259. package/dist/web-session.d.ts +11 -3
  260. package/dist/web-session.js +22 -11
  261. package/dist/web-session.js.map +1 -1
  262. package/dist/web-settings-patch.d.ts +1 -0
  263. package/dist/web-settings-patch.js +6 -3
  264. package/dist/web-settings-patch.js.map +1 -1
  265. package/dist/web-settings-tools.d.ts +0 -13
  266. package/dist/web-settings-tools.js +12 -84
  267. package/dist/web-settings-tools.js.map +1 -1
  268. package/launcher/agend-install.cjs +7 -1
  269. package/launcher/fleet-service-owner.cjs +150 -0
  270. package/package.json +1 -1
  271. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +0 -376
  272. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +0 -673
  273. package/dist/task-board-summary.d.ts +0 -101
  274. package/dist/task-board-summary.js +0 -177
  275. package/dist/task-board-summary.js.map +0 -1
  276. package/dist/ui/app-effects.js +0 -251
  277. package/dist/ui/chat-effect-posters.js +0 -42
  278. package/dist/ui/chat-effects.js +0 -167
  279. package/dist/ui/effect-trial-controller.js +0 -124
  280. package/dist/ui/effect-trial-offer.js +0 -2
  281. package/dist/ui/effect-trial-panel.js +0 -40
  282. package/dist/ui/effects-waker.js +0 -2
  283. package/dist/ui/settings-effects.js +0 -202
  284. package/dist/ui/settings-owner-effects.js +0 -82
  285. package/dist/ui/shared/effect-app-runtime.js +0 -299
  286. package/dist/ui/shared/effect-baseline.js +0 -64
  287. package/dist/ui/shared/effect-chat-runtime.js +0 -162
  288. package/dist/ui/shared/effect-cursor.js +0 -126
  289. package/dist/ui/shared/effect-display-metadata.js +0 -39
  290. package/dist/ui/shared/effect-frame.js +0 -146
  291. package/dist/ui/shared/effect-geometry.js +0 -137
  292. package/dist/ui/shared/effect-host-style.js +0 -10
  293. package/dist/ui/shared/effect-owner-regions.js +0 -74
  294. package/dist/ui/shared/effect-owner-source.js +0 -122
  295. package/dist/ui/shared/effect-policy.js +0 -151
  296. package/dist/ui/shared/effect-poster-reader.js +0 -98
  297. package/dist/ui/shared/effect-posters.js +0 -117
  298. package/dist/ui/shared/effect-profiler.js +0 -184
  299. package/dist/ui/shared/effect-profiles.js +0 -65
  300. package/dist/ui/shared/effect-registry.js +0 -545
  301. package/dist/ui/shared/effect-runtime-loader.js +0 -236
  302. package/dist/ui/shared/effect-safety.js +0 -153
  303. package/dist/ui/shared/effect-stock-snapshot.js +0 -43
  304. package/dist/ui/shared/effect-stock-trust.js +0 -217
  305. package/dist/ui/shared/effect-trial-shadow.js +0 -62
  306. package/dist/ui/shared/effect-trial-source.js +0 -103
  307. package/dist/ui/shared/effect-trial-stage.js +0 -170
  308. package/dist/ui/shared/effect-trial.css +0 -40
  309. package/dist/ui/shared/feature-flags.js +0 -127
  310. package/dist/ui/shared/owner-effect-prefs.js +0 -117
  311. package/dist/web-effect-bootstrap.d.ts +0 -12
  312. package/dist/web-effect-bootstrap.js +0 -49
  313. package/dist/web-effect-bootstrap.js.map +0 -1
  314. package/dist/web-effect-bundle.d.ts +0 -28
  315. package/dist/web-effect-bundle.js +0 -171
  316. package/dist/web-effect-bundle.js.map +0 -1
  317. package/dist/web-effect-canonical.d.ts +0 -8
  318. package/dist/web-effect-canonical.js +0 -27
  319. package/dist/web-effect-canonical.js.map +0 -1
  320. package/dist/web-effect-library.d.ts +0 -114
  321. package/dist/web-effect-library.js +0 -530
  322. package/dist/web-effect-library.js.map +0 -1
  323. package/dist/web-effect-playback.d.ts +0 -30
  324. package/dist/web-effect-playback.js +0 -71
  325. package/dist/web-effect-playback.js.map +0 -1
  326. package/dist/web-effect-poster.d.ts +0 -8
  327. package/dist/web-effect-poster.js +0 -52
  328. package/dist/web-effect-poster.js.map +0 -1
  329. package/dist/web-effect-profiles.d.ts +0 -118
  330. package/dist/web-effect-profiles.js +0 -181
  331. package/dist/web-effect-profiles.js.map +0 -1
  332. package/dist/web-effect-service.d.ts +0 -93
  333. package/dist/web-effect-service.js +0 -335
  334. package/dist/web-effect-service.js.map +0 -1
  335. package/dist/web-effect-tools.d.ts +0 -48
  336. package/dist/web-effect-tools.js +0 -129
  337. package/dist/web-effect-tools.js.map +0 -1
  338. package/dist/web-effect-trials.d.ts +0 -86
  339. package/dist/web-effect-trials.js +0 -204
  340. package/dist/web-effect-trials.js.map +0 -1
  341. package/dist/web-effect-upload.d.ts +0 -27
  342. package/dist/web-effect-upload.js +0 -185
  343. package/dist/web-effect-upload.js.map +0 -1
  344. package/dist/web-effect-worker.d.ts +0 -8
  345. package/dist/web-effect-worker.js +0 -170
  346. package/dist/web-effect-worker.js.map +0 -1
  347. package/dist/web-effect.d.ts +0 -103
  348. package/dist/web-effect.js +0 -153
  349. package/dist/web-effect.js.map +0 -1
  350. package/dist/web-experimental-http.d.ts +0 -2
  351. package/dist/web-experimental-http.js +0 -12
  352. package/dist/web-experimental-http.js.map +0 -1
  353. package/dist/web-mod-agent.d.ts +0 -50
  354. package/dist/web-mod-agent.js +0 -207
  355. package/dist/web-mod-agent.js.map +0 -1
@@ -1,236 +0,0 @@
1
- /** #1629: bounded host HTTP projection. No product startup, frame, content event or retry. */
2
- import { checkStaticEffectPoster } from "./effect-poster-reader.js";
3
- const loaders = new WeakMap();
4
- const ID = /^[a-f0-9]{32}$/, EPOCH = /^[a-f0-9]{16}$/, PARAM = /^[a-z][a-z0-9_]{0,31}$/;
5
- const REGIONS = ["chat", "sidebar", "header", "page", "box"], WANTS = ["pointer", "state", "reply", "mention", "theme"];
6
- const SLOTS = ["fx.sidebar", "fx.header", "fx.page", "fx.box"];
7
- const MIB = 1048576, WIRE = 8 * MIB;
8
- const plain = x => !!x && (Object.getPrototypeOf(x) === Object.prototype || Object.getPrototypeOf(x) === null);
9
- const finite = x => typeof x === "number" && Number.isFinite(x);
10
- const integer = (x, lo, hi) => Number.isSafeInteger(x) && x >= lo && x <= hi;
11
- const exact = (o, keys) => plain(o) && Object.keys(o).sort().join(",") === [...keys].sort().join(",");
12
- const subset = (o, keys) => plain(o) && Object.keys(o).every(k => keys.includes(k));
13
- const instanceOK = x => typeof x === "string" && x.length >= 1 && x.length <= 128;
14
- const enumeration = (x, values, min = 0) => Array.isArray(x) && x.length >= min && x.length <= values.length && new Set(x).size === x.length && x.every(v => values.includes(v));
15
- /** Copy own data before inspecting it. Refuse accessors/symbols, non-finite values and unbounded trees. */
16
- function data(value, maxNodes = 1024) {
17
- let nodes = 0;
18
- function copy(x, depth) {
19
- if (++nodes > maxNodes || depth > 8) throw Error("shape");
20
- if (x === null || typeof x === "string" || typeof x === "boolean" || finite(x)) return x;
21
- const array = Array.isArray(x);
22
- if (array ? Object.getPrototypeOf(x) !== Array.prototype || x.length > 32 : !plain(x)) throw Error("shape");
23
- const out = array ? [] : Object.create(null), keys = Reflect.ownKeys(x);
24
- if (keys.length > 33) throw Error("shape");
25
- for (const key of keys) {
26
- if (array && key === "length") continue;
27
- if (typeof key !== "string" || array && (!/^(0|[1-9][0-9]*)$/.test(key) || !Number.isSafeInteger(Number(key)) || Number(key) >= x.length)) throw Error("shape");
28
- const d = Object.getOwnPropertyDescriptor(x, key); if (!d || !("value" in d)) throw Error("shape");
29
- out[key] = copy(d.value, depth + 1);
30
- }
31
- if (array && Object.keys(out).length !== x.length) throw Error("shape");
32
- return Object.freeze(out);
33
- }
34
- try { return copy(value, 0); } catch { return null; }
35
- }
36
- // Same normalized one-line contract as web-effect.ts text()/web-theme.ts canonicalText.
37
- const FORBIDDEN_TEXT = /(?!\u200d)[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
38
- const VISIBLE_TEXT = /[^\p{M}\p{C}\p{Z}\u115f\u1160\u3164\uffa0\u2800]/u;
39
- let graphemes = null;
40
- function displayText(s, max, min = 1) {
41
- if (typeof s !== "string" || s.length > max * 10) return false;
42
- if (min === 0 && s === "") return true;
43
- if (s.normalize("NFC") !== s || FORBIDDEN_TEXT.test(s) || s.trim() !== s || !VISIBLE_TEXT.test(s)) return false;
44
- try {
45
- graphemes ??= new Intl.Segmenter(undefined, { granularity: "grapheme" });
46
- let count = 0; for (const _part of graphemes.segment(s)) if (++count > max) return false;
47
- return count >= min;
48
- } catch { return false; }
49
- }
50
- function manifest(x) {
51
- if (!subset(x, ["v", "kind", "runtime", "worker", "name", "title", "description", "poster", "regions", "layer", "box", "cursor", "wants", "params"]) || x.v !== 1 || x.kind !== "effect" || x.runtime !== "canvas-worker-v1" || x.worker !== "worker.js"
52
- || typeof x.name !== "string" || !/^[a-z0-9][a-z0-9-]{0,31}$/.test(x.name) || !displayText(x.title, 40)
53
- || x.description !== undefined && !displayText(x.description, 200, 0) || x.poster !== undefined && x.poster !== "poster.png"
54
- || !enumeration(x.regions, REGIONS, 1) || !["behind", "front"].includes(x.layer) || !enumeration(x.wants, WANTS)) return false;
55
- if (x.box !== undefined && (!exact(x.box, ["anchor", "width", "height", "offset"]) || !["top-left", "top-right", "bottom-left", "bottom-right", "center"].includes(x.box.anchor) || !integer(x.box.width, 32, 480) || !integer(x.box.height, 32, 480) || !integer(x.box.offset, 0, 64))) return false;
56
- if (x.cursor !== undefined && (!exact(x.cursor, ["hotspot"]) || !Array.isArray(x.cursor.hotspot) || x.cursor.hotspot.length !== 2 || !x.cursor.hotspot.every(v => integer(v, 0, 63)))) return false;
57
- if (x.params === undefined) return true;
58
- if (!plain(x.params) || Object.keys(x.params).length > 16) return false;
59
- return Object.entries(x.params).every(([k, d]) => {
60
- if (!PARAM.test(k) || !plain(d)) return false;
61
- if (d.type === "number" || d.type === "int") return subset(d, ["type", "min", "max", "default", ...(d.type === "number" ? ["step"] : [])]) && [d.min, d.max, d.default].every(v => finite(v) && Math.abs(v) <= 1e9 && (d.type !== "int" || Number.isInteger(v)))
62
- && d.min <= d.max && d.default >= d.min && d.default <= d.max && (d.step === undefined || finite(d.step) && d.step > 0 && d.step <= 2e9 && aligned(d.default, d));
63
- if (d.type === "bool") return exact(d, ["type", "default"]) && typeof d.default === "boolean";
64
- if (d.type === "color") return exact(d, ["type", "default"]) && typeof d.default === "string" && /^#[0-9a-f]{6}$/.test(d.default);
65
- return d.type === "enum" && exact(d, ["type", "default", "values"]) && Array.isArray(d.values) && d.values.length >= 1 && d.values.length <= 16 && new Set(d.values).size === d.values.length && d.values.every(v => typeof v === "string" && /^[a-z0-9][a-z0-9-]{0,31}$/.test(v)) && d.values.includes(d.default);
66
- });
67
- }
68
- function aligned(v, d) { const n = (v - d.min) / d.step; return Number.isFinite(n) && Math.abs(n - Math.round(n)) <= 1e-8; }
69
- function params(m, values) {
70
- const defs = m.params ?? {};
71
- return plain(values) && Object.keys(values).sort().join(",") === Object.keys(defs).sort().join(",") && Object.entries(defs).every(([key, d]) => {
72
- const v = values[key];
73
- if (d.type === "number" || d.type === "int") return finite(v) && v >= d.min && v <= d.max && (d.type !== "int" || Number.isSafeInteger(v)) && (d.step === undefined || aligned(v, d));
74
- if (d.type === "bool") return typeof v === "boolean";
75
- return d.type === "enum" ? d.values.includes(v) : typeof v === "string" && /^#[0-9a-f]{6}$/.test(v);
76
- });
77
- }
78
- /** HTTP data only: authoritative provenance comes from the host's persisted placement, never a manifest. */
79
- export function checkEffectPlacements(input) {
80
- const x = data(input); if (!exact(x, ["rev", "effects"]) || !integer(x.rev, 0, Number.MAX_SAFE_INTEGER) || !Array.isArray(x.effects) || x.effects.length > 2) return null;
81
- const ids = new Set();
82
- for (const p of x.effects) {
83
- if (!exact(p, ["id", "by", "at", "manifest", "params"]) || typeof p.id !== "string" || !ID.test(p.id) || ids.has(p.id) || !["human", "agent"].includes(p.by) || !integer(p.at, 0, Number.MAX_SAFE_INTEGER) || !manifest(p.manifest) || !params(p.manifest, p.params)) return null;
84
- ids.add(p.id);
85
- }
86
- return x;
87
- }
88
- /** Closed personal metadata. Uses the same CW1 manifest/text contract as resources, with no code or provenance grant. */
89
- export function checkEffectOwnerSlots(input) {
90
- const x = data(input, 4096), keys = [...SLOTS, "fx.cursor"];
91
- if (!exact(x, ["rev", "slots"]) || !integer(x.rev, 0, Number.MAX_SAFE_INTEGER) || !exact(x.slots, keys)) return null;
92
- for (const key of keys) {
93
- const row = x.slots[key];
94
- if (!plain(row)) return null;
95
- if (row.status === "none") { if (!exact(row, ["status", "id"]) || row.id !== null) return null; }
96
- else if (row.status === "unavailable") { if (!exact(row, ["status", "id"]) || typeof row.id !== "string" || !ID.test(row.id)) return null; }
97
- else if (row.status === "available") {
98
- if (!exact(row, ["status", "id", "manifest"]) || typeof row.id !== "string" || !ID.test(row.id) || !manifest(row.manifest)) return null;
99
- if (key === "fx.cursor" ? !row.manifest.cursor : !row.manifest.regions.includes(key.slice(3)) || key === "fx.box" && !row.manifest.box) return null;
100
- } else return null;
101
- }
102
- return x;
103
- }
104
- /** Checked local-admin Start response only; this projection never starts or authorizes a trial. */
105
- export function checkEffectTrialStart(input, expectedOffer, expectedId) {
106
- const x = data(input);
107
- if (typeof expectedOffer !== "string" || !ID.test(expectedOffer) || typeof expectedId !== "string" || !ID.test(expectedId)
108
- || !exact(x, ["trial", "offer", "remainingMs", "manifest", "params"]) || typeof x.trial !== "string" || !ID.test(x.trial)
109
- || !integer(x.remainingMs, 1, 600000) || !exact(x.offer, ["v", "offer", "id", "title", "expiresAt"])
110
- || x.offer.v !== 1 || x.offer.offer !== expectedOffer || x.offer.id !== expectedId || !displayText(x.offer.title, 40)
111
- || !integer(x.offer.expiresAt, 0, Number.MAX_SAFE_INTEGER) || !manifest(x.manifest) || x.manifest.title !== x.offer.title || !params(x.manifest, x.params)) return null;
112
- return x;
113
- }
114
-
115
- /** Never include host poster/cursor bytes in the Worker asset list. The factory snapshots again. */
116
- export function checkEffectResource(input, expectedId) {
117
- const x = data(input); if (!exact(x, ["id", "manifest", "params", "wants", "code", "assets"]) || typeof expectedId !== "string" || !ID.test(expectedId) || x.id !== expectedId || !manifest(x.manifest) || !params(x.manifest, x.params)
118
- || !enumeration(x.wants, WANTS) || JSON.stringify(x.wants) !== JSON.stringify(x.manifest.wants) || typeof x.code !== "string" || x.code.length > MIB || !Array.isArray(x.assets) || x.assets.length > 16) return null;
119
- const encoder = new TextEncoder(), code = encoder.encode(x.code); let bytes = code.length + encoder.encode(JSON.stringify(x.params)).length;
120
- if (bytes > MIB || new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(code) !== x.code) return null;
121
- const names = new Set(), assets = [];
122
- for (const a of x.assets) {
123
- if (!exact(a, ["name", "mime", "data_b64"]) || typeof a.name !== "string" || !/^assets\/[a-z0-9][a-z0-9_-]{0,63}\.(png|jpe?g|webp|gif)$/.test(a.name) || names.has(a.name) || typeof a.data_b64 !== "string" || a.data_b64.length > Math.ceil(MIB / 3) * 4 || a.data_b64.length % 4 !== 0) return null;
124
- const ext = a.name.slice(a.name.lastIndexOf(".") + 1), mime = ["jpg", "jpeg"].includes(ext) ? "image/jpeg" : `image/${ext}`;
125
- if (a.mime !== mime) return null;
126
- let raw; try { raw = atob(a.data_b64); if (btoa(raw) !== a.data_b64) return null; } catch { return null; }
127
- if (!raw.length || (bytes += raw.length) > MIB) return null;
128
- names.add(a.name); assets.push({ name: a.name, mime, bytes: Uint8Array.from(raw, c => c.charCodeAt(0)) });
129
- }
130
- return { id: x.id, manifest: x.manifest, params: x.params, wants: x.wants, code: x.code, assets };
131
- }
132
-
133
- /** Static host cursor only. Native decode/hotspot checks remain the mount controller's responsibility. */
134
- export function checkStaticEffectCursor(bytes) {
135
- if (!(bytes instanceof Uint8Array) || bytes.length > 32768) return null;
136
- const image = checkStaticEffectPoster(bytes); if (!image) return null;
137
- const view = new DataView(image.buffer, image.byteOffset, image.byteLength), width = view.getUint32(16), height = view.getUint32(20);
138
- return width >= 1 && width <= 64 && height === width ? image : null;
139
- }
140
-
141
- /** The host freezes ownerEpoch/current at page creation; neither is taken from a response or an effect. */
142
- export function createEffectRuntimeLoader({ window: host, ownerEpoch, current, onRevoke = () => {} } = {}) {
143
- if (!host || typeof host !== "object" || loaders.has(host) || typeof host.fetch !== "function" || typeof host.AbortController !== "function" || typeof host.setTimeout !== "function" || typeof host.clearTimeout !== "function" || typeof ownerEpoch !== "string" || !EPOCH.test(ownerEpoch) || typeof current !== "function" || typeof onRevoke !== "function") return { ok: false };
144
- const reservation = {}; loaders.set(host, reservation);
145
- let dead = false, revoked = false, previous = null; const pending = new Set();
146
- let finishDisposed; const disposed = new Promise(resolve => { finishDisposed = resolve; });
147
- const releaseWindow = () => { if (dead && pending.size === 0) { if (loaders.get(host) === reservation) loaders.delete(host); finishDisposed(); } };
148
- // Terminal state is latched before callbacks. Abort releases logical callers, never physical reservations.
149
- function revoke(reason) {
150
- if (dead || revoked) return;
151
- revoked = true;
152
- try { onRevoke(Object.freeze({ reason })); } catch { /* host notification cannot preserve authority */ }
153
- for (const p of [...pending]) { try { p.abort?.(); } catch { /* cancel the other operations */ } }
154
- }
155
- const now = () => { let t; try { t = host.performance.now(); } catch { revoke("clock_unavailable"); return null; } if (!finite(t) || t < 0 || previous !== null && t < previous) { revoke("clock_unavailable"); return null; } previous = t; return t; };
156
- const alive = () => { if (dead || revoked) return false; try { if (current() === true) return !dead && !revoked; } catch { /* unknown */ } revoke("owner_unavailable"); return false; };
157
- if (!alive() || now() === null) { dead = true; releaseWindow(); return { ok: false }; }
158
- function run(kind, path, project, signal, settled = false, binary = false) {
159
- if (dead || revoked) return Promise.resolve(null);
160
- const began = now(); if (began === null) return Promise.resolve(null);
161
- if (!alive() || signal?.aborted || [...pending].filter(p => p.kind === kind).length >= (kind === "resource" ? 3 : 1)) return Promise.resolve(null);
162
- const controller = new host.AbortController(), op = { kind, controller, abort: null }, limit = binary ? 32768 : kind === "placements" ? 256 * 1024 : WIRE; pending.add(op);
163
- let release, reader = null, body = null, cancellation = null, timer = null, wasAborted = false;
164
- // Cache the actual cancellation, but not a pre-response no-op: a late response still owns its body.
165
- const cancelBody = () => {
166
- if (!reader && !body) return Promise.resolve();
167
- if (!cancellation) { try { cancellation = Promise.resolve(reader ? reader.cancel() : body.cancel()).catch(() => {}); } catch { cancellation = Promise.resolve(); } }
168
- return cancellation;
169
- };
170
- const cancelled = new Promise(resolve => { release = resolve; });
171
- const abort = () => { wasAborted = true; controller.abort(); void cancelBody(); release(null); };
172
- op.abort = abort;
173
- const valid = () => { if (!alive() || controller.signal.aborted) { abort(); return false; } const t = now(); if (t === null || t < began || t - began >= 10000) { abort(); return false; } return true; };
174
- const work = Promise.resolve().then(async () => {
175
- if (!valid()) return null;
176
- const response = await host.fetch(path, { credentials: "same-origin", cache: "no-store", redirect: "error", signal: controller.signal });
177
- body = response.body;
178
- if (!valid()) return null;
179
- if ([401, 403, 409].includes(response.status)) { revoke("authorization_rejected"); return null; }
180
- if (!response.ok || response.redirected || !((binary ? /^image\/png(?:\s*;|$)/i : /^application\/json(?:\s*;|$)/i).test(response.headers.get("content-type") || ""))) return null;
181
- const length = response.headers.get("content-length");
182
- if (length !== null && (!/^(0|[1-9][0-9]*)$/.test(length) || !integer(Number(length), 0, limit))) return null;
183
- if (!body || typeof body.getReader !== "function") return null;
184
- reader = body.getReader(); const chunks = []; let size = 0;
185
- for (;;) {
186
- const part = await reader.read(); if (!valid()) return null;
187
- if (part.done) break;
188
- if (!(part.value instanceof Uint8Array) || (size += part.value.length) > limit) { abort(); return null; }
189
- chunks.push(part.value.slice());
190
- }
191
- const bytes = new Uint8Array(size); let offset = 0; for (const part of chunks) { bytes.set(part, offset); offset += part.length; }
192
- let value = bytes; if (!binary) { try { value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)); } catch { return null; } }
193
- if (!valid()) return null;
194
- const answer = project(value); return valid() ? answer : null;
195
- }).catch(() => null).finally(async () => { controller.abort(); await cancelBody(); pending.delete(op); releaseWindow(); host.clearTimeout(timer); signal?.removeEventListener("abort", abort); }).then(value => {
196
- // Cleanup aborts the controller even on success; only an actual abort or stale completion revokes the result.
197
- if (!alive() || wasAborted || signal?.aborted) return null;
198
- const t = now(); return t !== null && t >= began && t - began < 10000 ? value : null;
199
- });
200
- timer = host.setTimeout(abort, 10000); signal?.addEventListener("abort", abort, { once: true });
201
- return settled ? work : Promise.race([work, cancelled]);
202
- }
203
- const placementRead = (instance, signal, settled = false) => instanceOK(instance) ? run("placements", `/ui/effects/placements?instance=${encodeURIComponent(instance)}&ownerEpoch=${ownerEpoch}`, checkEffectPlacements, signal, settled) : Promise.resolve(null);
204
- function resourceRead(input, signal, settled = false) {
205
- const spec = data(input);
206
- if (!subset(spec, ["id", "instance", "slot"]) || typeof spec.id !== "string" || !ID.test(spec.id) || (spec.instance !== undefined) === (spec.slot !== undefined) || spec.instance !== undefined && !instanceOK(spec.instance) || spec.slot !== undefined && !SLOTS.includes(spec.slot)) return Promise.resolve(null);
207
- const id = spec.id, scope = spec.instance !== undefined ? `instance=${encodeURIComponent(spec.instance)}` : `slot=${encodeURIComponent(spec.slot)}`;
208
- return run("resource", `/ui/effects/resource/${id}?${scope}&ownerEpoch=${ownerEpoch}`, value => checkEffectResource(value, id), signal, settled);
209
- }
210
- function trialResourceRead(input, signal) {
211
- const spec = data(input);
212
- if (!exact(spec, ["trial", "id", "instance"]) || typeof spec.trial !== "string" || !ID.test(spec.trial)
213
- || typeof spec.id !== "string" || !ID.test(spec.id) || !instanceOK(spec.instance)) return Promise.resolve(null);
214
- const id = spec.id;
215
- return run("resource", `/ui/effect-trials/${spec.trial}/resource?instance=${encodeURIComponent(spec.instance)}&ownerEpoch=${ownerEpoch}`, value => checkEffectResource(value, id), signal, true);
216
- }
217
- const ownerRead = (signal, settled = false) => run("placements", `/ui/effects/owner?ownerEpoch=${ownerEpoch}`, checkEffectOwnerSlots, signal, settled);
218
- return { ok: true,
219
- whenDisposed: () => disposed,
220
- owner(signal) { return ownerRead(signal); },
221
- placements(instance, signal) { return placementRead(instance, signal); },
222
- resource(input, signal) { return resourceRead(input, signal); },
223
- // Host scheduler seams: logical abort is prompt; these promises retain ownership until physical cleanup.
224
- ownerSettled(signal) { return ownerRead(signal, true); },
225
- placementsSettled(instance, signal) { return placementRead(instance, signal, true); },
226
- resourceSettled(input, signal) { return resourceRead(input, signal, true); },
227
- trialResourceSettled(input, signal) { return trialResourceRead(input, signal); },
228
- // One binary physical lane in the same loader: no response-derived URL, code, metadata or ACL fallback.
229
- cursorImageSettled(id, signal) {
230
- return typeof id === "string" && ID.test(id)
231
- ? run("cursor", `/ui/effects/image/${id}/cursor.png?slot=fx.cursor&ownerEpoch=${ownerEpoch}`, checkStaticEffectCursor, signal, true, true)
232
- : Promise.resolve(null);
233
- },
234
- dispose() { if (dead) return; dead = true; for (const p of pending) p.abort(); releaseWindow(); },
235
- };
236
- }
@@ -1,153 +0,0 @@
1
- /** #1629: synchronous browser probation/history participant. No product startup or frame creation. */
2
- import { newEffectPauseHistory, recordEffectPause } from "./effect-profiler.js";
3
-
4
- const PREFIX = "agend_effect_safety_v1:", MAX_KEYS = 4096, MAX_RECORDS = 2048, MAX_BYTES = 512 * 1024;
5
- const plain = o => !!o && (Object.getPrototypeOf(o) === Object.prototype || Object.getPrototypeOf(o) === null);
6
- const idOK = x => typeof x === "string" && /^[a-f0-9]{32}$/.test(x);
7
- const deviceOK = x => x === "standard" || x === "constrained";
8
- const wallOK = x => typeof x === "number" && Number.isSafeInteger(x) && x >= 0;
9
- const fields = (o, expected) => plain(o) && Object.keys(o).sort().join(",") === expected.split(",").sort().join(",");
10
- const keyFor = r => `${PREFIX}${r.kind}:${r.id}:${r.device}:${r.token}`;
11
- function valid(r) {
12
- if (!plain(r) || r.v !== 1 || !idOK(r.id) || !deviceOK(r.device) || !idOK(r.token)) return false;
13
- if (r.kind === "probation") return fields(r, "v,kind,id,device,token,owner,at") && idOK(r.owner) && wallOK(r.at);
14
- if (r.kind === "pause") return fields(r, "v,kind,id,device,token,at") && wallOK(r.at);
15
- return r.kind === "resume" && fields(r, "v,kind,id,device,token");
16
- }
17
-
18
- /**
19
- * Storage is local to this browser/origin. Append-only pause receipts avoid read/modify/write lost updates
20
- * between tabs. Explicit human Resume acknowledges exact pause ids; it never deletes history or sticky off.
21
- * Any unreadable/unknown receipt, quota failure or incomplete cleanup latches this participant closed.
22
- */
23
- export function createEffectSafety({ storage, crypto, wall = () => Date.now() } = {}) {
24
- let fault = false, owner = null;
25
- const owned = new Map();
26
- const fail = () => { fault = true; return null; };
27
- const nonce = () => {
28
- try {
29
- const bytes = new Uint8Array(16); if (crypto.getRandomValues(bytes) !== bytes) return fail();
30
- return [...bytes].map(x => x.toString(16).padStart(2, "0")).join("");
31
- } catch { return fail(); }
32
- };
33
- try {
34
- if (!storage || ["key", "getItem", "setItem", "removeItem"].some(k => typeof storage[k] !== "function") || typeof crypto?.getRandomValues !== "function" || typeof wall !== "function") fault = true;
35
- else owner = nonce();
36
- } catch { fault = true; }
37
- function read() {
38
- if (fault) return null;
39
- try {
40
- const length = storage.length;
41
- if (!Number.isSafeInteger(length) || length < 0 || length > MAX_KEYS) return fail();
42
- const rows = new Map(), seen = new Set(); let bytes = 0;
43
- for (let i = 0; i < length; i++) {
44
- const key = storage.key(i);
45
- if (typeof key !== "string" || seen.has(key)) return fail();
46
- seen.add(key);
47
- if (!key.startsWith(PREFIX)) continue;
48
- const text = storage.getItem(key);
49
- if (typeof text !== "string" || text.length > 1024) return fail();
50
- bytes += (key.length + text.length) * 2;
51
- if (bytes > MAX_BYTES || rows.size >= MAX_RECORDS) return fail();
52
- const value = JSON.parse(text);
53
- if (!valid(value) || keyFor(value) !== key) return fail();
54
- rows.set(key, { value, text });
55
- }
56
- if (storage.length !== length) return fail();
57
- // A still-owned probation receipt cannot disappear or change beneath a live page. Another tab's
58
- // explicit Resume may remove it; that page must stop rather than silently treating the loss as healthy.
59
- for (const item of owned.values()) if (rows.get(item.key)?.text !== item.text) return fail();
60
- for (const { value: r } of rows.values()) if (r.kind === "resume" && !rows.has(keyFor({ ...r, kind: "pause" }))) return fail();
61
- return rows;
62
- } catch { return fail(); }
63
- }
64
- function inspect(rows, id, device) {
65
- const relevant = [...rows.values()].map(x => x.value).filter(r => r.id === id && r.device === device);
66
- const pauses = relevant.filter(r => r.kind === "pause").sort((a, b) => a.at - b.at || a.token.localeCompare(b.token));
67
- let history = newEffectPauseHistory(id, device, 0);
68
- for (const r of pauses) { const next = recordEffectPause(history, r.at); if (!next.ok) return fail(); history = next.history; }
69
- const interrupted = relevant.some(r => r.kind === "probation" && r.owner !== owner);
70
- const paused = pauses.some(r => !rows.has(keyFor({ ...r, kind: "resume" })));
71
- return { state: history.off ? "off" : interrupted ? "interrupted" : paused ? "paused" : "ok", history };
72
- }
73
- function put(record, rows) {
74
- try {
75
- const key = keyFor(record), text = JSON.stringify(record), previous = storage.getItem(key);
76
- if (previous !== null) { if (previous === text) return true; return !!fail(); }
77
- if (rows.size >= MAX_RECORDS || [...rows].reduce((n, [k, r]) => n + (k.length + r.text.length) * 2, (key.length + text.length) * 2) > MAX_BYTES) return !!fail();
78
- storage.setItem(key, text);
79
- if (storage.getItem(key) !== text) return !!fail();
80
- return true;
81
- } catch { return !!fail(); }
82
- }
83
- function remove(key, text) {
84
- try {
85
- if (storage.getItem(key) !== text) return !!fail();
86
- storage.removeItem(key);
87
- if (storage.getItem(key) !== null) return !!fail();
88
- return true;
89
- } catch { return !!fail(); }
90
- }
91
- function stamp(rows, id, device) {
92
- try {
93
- const now = wall(); if (!wallOK(now)) return fail();
94
- return Math.max(now, ...[...rows.values()].map(x => x.value).filter(r => r.id === id && r.device === device && "at" in r).map(r => r.at));
95
- } catch { return fail(); }
96
- }
97
- const status = (id, device) => {
98
- if (!idOK(id) || !deviceOK(device)) return { state: "unknown" };
99
- const rows = read(), result = rows && inspect(rows, id, device);
100
- return result ? { state: result.state, history: { ...result.history, pauses: [...result.history.pauses] } } : { state: "unknown" };
101
- };
102
- return {
103
- status,
104
- allowed(id, device) { return status(id, device).state === "ok"; },
105
- begin(spec) {
106
- if (!plain(spec) || !idOK(spec.id) || !deviceOK(spec.device) || typeof spec.key !== "string" || spec.key.length < 1 || spec.key.length > 256) return null;
107
- const rows = read(), state = rows && inspect(rows, spec.id, spec.device);
108
- if (!state || state.state !== "ok") return null;
109
- const token = nonce(), at = stamp(rows, spec.id, spec.device);
110
- if (token === null || at === null) return null;
111
- const record = { v: 1, kind: "probation", id: spec.id, device: spec.device, token, owner, at };
112
- // Never overwrite a colliding marker, even one this page already owns.
113
- if (rows.has(keyFor(record))) return fail();
114
- if (!put(record, rows)) return null;
115
- const fresh = read(), verified = fresh && inspect(fresh, spec.id, spec.device);
116
- if (!verified || verified.state !== "ok" || fresh.get(keyFor(record))?.text !== JSON.stringify(record)) { if (!fault) remove(keyFor(record), JSON.stringify(record)); return null; }
117
- owned.set(token, { key: keyFor(record), text: JSON.stringify(record) });
118
- return token;
119
- },
120
- finish(token, reason) {
121
- if (fault || !idOK(token) || typeof reason !== "string" || reason.length < 1 || reason.length > 128) return false;
122
- const item = owned.get(token); if (!item) return false;
123
- if (!remove(item.key, item.text)) return false;
124
- owned.delete(token); return true;
125
- },
126
- pause(record) {
127
- if (!plain(record) || !idOK(record.id) || !deviceOK(record.device) || record.reason !== "host_budget") return false;
128
- const rows = read(); if (!rows) return false;
129
- const token = nonce(), at = stamp(rows, record.id, record.device);
130
- if (token === null || at === null) return false;
131
- const value = { v: 1, kind: "pause", id: record.id, device: record.device, token, at };
132
- if (rows.has(keyFor(value))) return !!fail();
133
- return put(value, rows);
134
- },
135
- /** Human action only: acknowledges the observed pauses, leaving every immutable history receipt intact. */
136
- resume(id, device) {
137
- if (!idOK(id) || !deviceOK(device)) return false;
138
- const rows = read(), state = rows && inspect(rows, id, device);
139
- if (!state || state.state === "off") return false;
140
- for (const [key, { value: r, text }] of rows) {
141
- if (r.id !== id || r.device !== device) continue;
142
- if (r.kind === "probation" && r.owner !== owner && !remove(key, text)) return false;
143
- if (r.kind === "pause" && !rows.has(keyFor({ ...r, kind: "resume" }))) {
144
- const ack = { v: 1, kind: "resume", id, device, token: r.token };
145
- if (!put(ack, rows)) return false;
146
- rows.set(keyFor(ack), { value: ack, text: JSON.stringify(ack) });
147
- }
148
- }
149
- const fresh = read(), verified = fresh && inspect(fresh, id, device);
150
- return verified?.state === "ok";
151
- },
152
- };
153
- }
@@ -1,43 +0,0 @@
1
- /** #1629: a bounded copy of host stock paint evidence. Never reads an accessor or a DOMRect. */
2
- const snapshots = new WeakSet();
3
- const plain = value => !!value && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
4
- const finite = value => typeof value === "number" && Number.isFinite(value) && Math.abs(value) <= 1e7;
5
- function fields(value, required, optional = []) {
6
- if (!plain(value)) return null;
7
- const descriptors = Object.getOwnPropertyDescriptors(value), keys = Reflect.ownKeys(descriptors);
8
- if (keys.some(key => typeof key !== "string" || !required.includes(key) && !optional.includes(key) || !Object.hasOwn(descriptors[key], "value")) || required.some(key => !Object.hasOwn(descriptors, key))) return null;
9
- return Object.fromEntries(keys.map(key => [key, descriptors[key].value]));
10
- }
11
- export function effectStockSnapshot(value) {
12
- try {
13
- if (snapshots.has(value)) return value;
14
- const data = fields(value, ["ok", "front", "agentFront", "rects"], ["reason", "agentReason", "settingsHref"]);
15
- if (!data || data.ok !== true || typeof data.front !== "boolean" || typeof data.agentFront !== "boolean" || data.agentFront && !data.front || !Array.isArray(data.rects) || data.rects.length > 128) return null;
16
- // Optional host display metadata is not copied into the paint evidence.
17
- for (const key of ["reason", "agentReason", "settingsHref"]) if (Object.hasOwn(data, key) && data[key] !== null && (typeof data[key] !== "string" || data[key].length > 256)) return null;
18
- const entries = Object.getOwnPropertyDescriptors(data.rects), keys = Reflect.ownKeys(entries);
19
- if (keys.length !== data.rects.length + 1 || keys.some(key => key !== "length" && (typeof key !== "string" || !/^(0|[1-9]\d*)$/.test(key) || Number(key) >= data.rects.length || !Object.hasOwn(entries[key], "value")))) return null;
20
- const rects = [];
21
- for (let i = 0; i < data.rects.length; i++) {
22
- const r = fields(entries[i].value, ["x", "y", "width", "height"]);
23
- if (!r || ![r.x, r.y, r.width, r.height].every(finite) || r.width < 0 || r.height < 0) return null;
24
- rects.push(Object.freeze(r));
25
- }
26
- const result = Object.freeze({ ok: true, front: data.front, agentFront: data.agentFront, rects: Object.freeze(rects) });
27
- snapshots.add(result); return result;
28
- } catch { return null; }
29
- }
30
- /** Missing provenance receives the agent rule. Behind still requires proven stock ordering. */
31
- export function effectStockAllows(snapshot, { layer, by } = {}) {
32
- const proof = effectStockSnapshot(snapshot);
33
- return !!proof && (layer === "behind" || layer === "front" && proof.front && (by === "human" || proof.agentFront));
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
- }