@omnivoice/page-agent 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SimonLuvRamen
4
+ Copyright (c) 2026 Alibaba Group Holding Limited
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md CHANGED
@@ -1,13 +1,85 @@
1
- # PageAgent Lab — Fork with Voice Integration
1
+ # @omnivoice/page-agent
2
2
 
3
- Laboratório local para observar o `page-agent` controlando uma página real, com formulário, animações, cursor visual, console de debug e proxy seguro para OpenAI.
3
+ Agente de GUI para aplicações web: um script (ou um `mountWidget()`) dá a qualquer página o seu próprio agente que
4
+ lê o DOM e age nele. Este pacote é um fork do [`page-agent`](https://github.com/alibaba/page-agent) (Alibaba, MIT) com
5
+ um widget central, um gateway que guarda a chave do provedor e um módulo de voz **opcional**.
4
6
 
5
- **Recursos desta fork:**
6
- - OpenAI Realtime full-duplex voice integration
7
- - Suporte a motores local (on-device) e realtime (cloud)
8
- - Documentação em `src/voice/README.md`
7
+ ## Instalação
9
8
 
10
- ## Rodar
9
+ ```bash
10
+ npm install @omnivoice/page-agent zod
11
+ ```
12
+
13
+ `zod` (`^3.25.0 || ^4.0.0`) é peer dependency. O pacote não depende de `@omnivoice/core`, `llms`, `page-controller`
14
+ nem `ui`: esses módulos vão embutidos no build.
15
+
16
+ ## Uso (ESM)
17
+
18
+ ```ts
19
+ import { mountWidget } from "@omnivoice/page-agent";
20
+
21
+ const widget = mountWidget({
22
+ model: "gpt-4o-mini",
23
+ baseURL: "https://seu-gateway.exemplo.com/v1",
24
+ language: "pt-BR",
25
+ });
26
+
27
+ await widget.run("Preencha o nome com Maria Oliveira");
28
+ ```
29
+
30
+ `mountWidget` só cria o botão lançador. O agente é criado ao abrir o painel (ou em `widget.ready()` / `widget.run()`).
31
+ A voz do navegador (`createVoice`) só é criada nessa hora, e o reconhecimento de fala só abre quando a pessoa clica
32
+ no botão de microfone. Para desligá-la: `voice: { input: false, output: false }`.
33
+
34
+ Também são exportados `PageAgent`, `createVoice`, `tool` e os tipos `PageAgentConfig`, `Widget`, `WidgetOptions`,
35
+ `Voice` e `VoiceOptions`.
36
+
37
+ ## Uso por CDN (IIFE)
38
+
39
+ ```html
40
+ <script src="https://cdn.jsdelivr.net/npm/@omnivoice/page-agent@VERSAO/dist/iife/page-agent.demo.js"
41
+ data-auto-init="false"></script>
42
+ <script>
43
+ window.PageAgent.mountWidget({ model: "gpt-4o-mini", baseURL: "/agent/v1" });
44
+ </script>
45
+ ```
46
+
47
+ Sem `data-auto-init="false"` (ou com `?autoInit` diferente de `false` na URL do script) o script cria um agente de
48
+ demonstração ao carregar. Troque `VERSAO` por uma versão publicada.
49
+
50
+ ## Gateway (chave do provedor só no servidor)
51
+
52
+ ```bash
53
+ AGENT_API_KEY=... AGENT_BASE_URL=https://api.openai.com/v1 AGENT_MODEL=gpt-4o-mini npx omnivoice-agent-gateway
54
+ ```
55
+
56
+ O gateway expõe `GET /config`, `POST /v1/chat/completions` e `GET /health`, e injeta a chave ao chamar o provedor.
57
+ As variáveis `AGENT_*` estão descritas no cabeçalho de `server/agent-gateway.mjs`.
58
+
59
+ ## Voz em tempo real (opcional)
60
+
61
+ `@omnivoice/page-agent/voice` é um módulo à parte. **Importar o agente não o carrega**, e importá-lo, criar a
62
+ configuração ou criar motores não liga microfone, conexão de áudio nem síntese: isso só acontece quando você chama
63
+ `createVoiceSession(...)`. Veja o uso, os modos (`local` e `realtime`), a configuração e o protocolo em
64
+ [`src/voice/README.md`](./src/voice/README.md).
65
+
66
+ ## Verificação do pacote
67
+
68
+ ```bash
69
+ npm ci
70
+ npm run build:package # dist/esm, dist/iife, dist/voice e server/
71
+ npm run verify:package # identidade, tarball efetivo, dist atual, tipos
72
+ npm run test:consumer # empacota e testa o tarball num projeto novo
73
+ ```
74
+
75
+ `npm publish` roda o build e o guard em modo `--release` antes de enviar.
76
+
77
+ ## Desenvolvimento do laboratório
78
+
79
+ O restante deste documento descreve o laboratório local deste repositório (`index.html`, `src/main.ts`, proxy
80
+ `server.mjs`), que consome o fork vendorizado em `vendor/page-agent` e não o pacote npm.
81
+
82
+ ### Rodar
11
83
 
12
84
  Requisitos: Node `^22.22.1 || >=24` e npm `^11.6.3`, mais uma conexão para instalar os pacotes.
13
85
 
@@ -16,7 +88,7 @@ O piso de versão vem de `vendor/page-agent`, não do laboratório: o monorepo d
16
88
  verifica essa precondição antes de comparar os artefatos.
17
89
 
18
90
  ```bash
19
- cd /home/bugson/PycharmProjects/test-pageagent
91
+ cd <raiz deste repositório>
20
92
  cp .env.example .env
21
93
  # edite .env e coloque OPENAI_API_KEY=...
22
94
  npm install
@@ -32,13 +104,13 @@ Abra <http://127.0.0.1:8797>.
32
104
 
33
105
  O mesmo processo serve o frontend e o proxy da API. O endpoint `http://127.0.0.1:8797/api/health` mostra se a chave foi carregada. A porta `8787` pertence a outro serviço (`agent-relay`) e não é usada pelo laboratório.
34
106
 
35
- ## Suíte de desafios
107
+ ### Suíte de desafios
36
108
 
37
109
  O laboratório agora contém mais de 100 desafios locais em 10 categorias: formulários, navegação, loading, recuperação, DOM profundo, dados, permissões, interação visual, adversarial e limites. O seletor mostra no máximo 12 cards por vez para não poluir o DOM que o PageAgent precisa observar; todos continuam disponíveis por busca e filtro.
38
110
 
39
111
  Cada desafio tem uma tarefa, uma dificuldade, um alvo renderizado, reset determinístico e evidência esperada. Os casos de cross-origin, gesto real e canvas são representados como limites explícitos da superfície local, não como falsos sucessos.
40
112
 
41
- ## Comandos do laboratório
113
+ ### Comandos do laboratório
42
114
 
43
115
  Para não precisar controlar processos manualmente:
44
116
 
@@ -54,7 +126,7 @@ make test # executa os testes Playwright
54
126
 
55
127
  O navegador recebe apenas `local-browser-session`. A chave real fica no `.env` e é usada pelo `server.mjs`, que encaminha chamadas para `https://api.openai.com/v1/chat/completions`.
56
128
 
57
- ## Como testar
129
+ ### Como testar
58
130
 
59
131
  1. Clique em `Preencher nome`, `Escolher especialidade` ou `Clicar em agendar` para preparar uma tarefa.
60
132
  2. Clique em `Executar com PageAgent`.
@@ -69,7 +141,7 @@ Se aparecer `EADDRINUSE` na porta `8797`, não troque a porta: verifique qual pr
69
141
 
70
142
  O `make down` encerra somente a sessão registrada em `.pageagent.pid`; ele não encerra serviços externos.
71
143
 
72
- ## Testes automatizados
144
+ ### Testes automatizados
73
145
 
74
146
  ```bash
75
147
  npx playwright install chromium
@@ -78,7 +150,7 @@ npm test
78
150
 
79
151
  Os testes cobrem o carregamento da página, acessibilidade básica, envio do formulário, animação do cursor e botões de comando rápido. A execução do LLM não é incluída no teste automatizado para não consumir créditos.
80
152
 
81
- ## Arquivos principais
153
+ ### Arquivos principais
82
154
 
83
155
  - `src/main.ts`: instancia o `PageAgent` real e monta o laboratório.
84
156
  - `src/durable-runtime.ts`: checkpoint IndexedDB, lease por aba, recuperação após `pagehide` e proteção contra replay cego.
@@ -90,11 +162,11 @@ Os testes cobrem o carregamento da página, acessibilidade básica, envio do for
90
162
  - `tests/lab.spec.ts`: testes Playwright.
91
163
  - `vite.config.ts`: encaminha `/api` para o proxy local.
92
164
 
93
- ## Evidência em tempo real
165
+ ### Evidência em tempo real
94
166
 
95
167
  Durante cada execução, o laboratório inicia um `ActionJournal` antes do agente. Ele captura `input`, `change`, `click`, `submit` e mutações relevantes do DOM enquanto a ferramenta está executando. A evidência é coalescida por `actionId` e adicionada ao histórico do próximo passo; ela indica que houve alteração, mas o sucesso final continua dependendo do verificador do desafio.
96
168
 
97
- ## Retomada e recursos do navegador
169
+ ### Retomada e recursos do navegador
98
170
 
99
171
  O widget agora cria um `runId` e salva checkpoints locais em IndexedDB. Em refresh ou navegação completa, a última ação fica como `unknown`; o agente não a repete automaticamente sem reconciliação. O lease em `localStorage` evita duas abas executarem o mesmo run, e `pagehide`/`pageshow` fazem a transição do documento.
100
172
 
@@ -0,0 +1,379 @@
1
+ (function() {
2
+ "use strict";
3
+ try {
4
+ if (typeof document != "undefined") {
5
+ var elementStyle = document.createElement("style");
6
+ elementStyle.appendChild(document.createTextNode(`._wrapper_1ooyb_1 {
7
+ position: fixed;
8
+ inset: 0;
9
+ z-index: 2147483641; /* 确保在所有元素之上,除了 panel */
10
+ cursor: wait;
11
+ overflow: hidden;
12
+
13
+ display: none;
14
+ }
15
+
16
+ ._wrapper_1ooyb_1._visible_1ooyb_11 {
17
+ display: block;
18
+ }
19
+ /* AI 光标样式 */
20
+ ._cursor_1dgwb_2 {
21
+ position: absolute;
22
+ width: var(--cursor-size, 75px);
23
+ height: var(--cursor-size, 75px);
24
+ pointer-events: none;
25
+ z-index: 10000;
26
+ }
27
+
28
+ ._cursorBorder_1dgwb_10 {
29
+ position: absolute;
30
+ width: 100%;
31
+ height: 100%;
32
+ background: linear-gradient(45deg, rgb(57, 182, 255), rgb(189, 69, 251));
33
+ mask-image: url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20100%20100'%20fill='none'%3e%3cg%3e%3cpath%20d='M%2015%2042%20L%2015%2036.99%20Q%2015%2031.99%2023.7%2031.99%20L%2028.05%2031.99%20Q%2032.41%2031.99%2032.41%2021.99%20L%2032.41%2017%20Q%2032.41%2012%2041.09%2016.95%20L%2076.31%2037.05%20Q%2085%2042%2076.31%2046.95%20L%2041.09%2067.05%20Q%2032.41%2072%2032.41%2062.01%20L%2032.41%2057.01%20Q%2032.41%2052.01%2023.7%2052.01%20L%2019.35%2052.01%20Q%2015%2052.01%2015%2047.01%20Z'%20fill='none'%20stroke='%23000000'%20stroke-width='6'%20stroke-miterlimit='10'%20style='stroke:%20light-dark(rgb(0,%200,%200),%20rgb(255,%20255,%20255));'/%3e%3c/g%3e%3c/svg%3e");
34
+ mask-size: 100% 100%;
35
+ mask-repeat: no-repeat;
36
+
37
+ transform-origin: center;
38
+ transform: rotate(-135deg) scale(1.2);
39
+ margin-left: -10px;
40
+ margin-top: -18px;
41
+ }
42
+
43
+ ._cursorFilling_1dgwb_25 {
44
+ position: absolute;
45
+ width: 100%;
46
+ height: 100%;
47
+ background: url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20100%20100'%3e%3cdefs%3e%3c/defs%3e%3cg%20xmlns='http://www.w3.org/2000/svg'%20style='filter:%20drop-shadow(light-dark(rgba(0,%200,%200,%200.4),%20rgba(237,%20237,%20237,%200.4))%203px%204px%204px);'%3e%3cpath%20d='M%2015%2042%20L%2015%2036.99%20Q%2015%2031.99%2023.7%2031.99%20L%2028.05%2031.99%20Q%2032.41%2031.99%2032.41%2021.99%20L%2032.41%2017%20Q%2032.41%2012%2041.09%2016.95%20L%2076.31%2037.05%20Q%2085%2042%2076.31%2046.95%20L%2041.09%2067.05%20Q%2032.41%2072%2032.41%2062.01%20L%2032.41%2057.01%20Q%2032.41%2052.01%2023.7%2052.01%20L%2019.35%2052.01%20Q%2015%2052.01%2015%2047.01%20Z'%20fill='%23ffffff'%20stroke='none'%20style='fill:%20%23ffffff;'/%3e%3c/g%3e%3c/svg%3e");
48
+ background-size: 100% 100%;
49
+ background-repeat: no-repeat;
50
+
51
+ transform-origin: center;
52
+ transform: rotate(-135deg) scale(1.2);
53
+ margin-left: -10px;
54
+ margin-top: -18px;
55
+ }
56
+
57
+ ._cursorRipple_1dgwb_39 {
58
+ position: absolute;
59
+ width: 100%;
60
+ height: 100%;
61
+ pointer-events: none;
62
+ margin-left: -50%;
63
+ margin-top: -50%;
64
+
65
+ &::after {
66
+ content: '';
67
+ opacity: 0;
68
+ position: absolute;
69
+ inset: 0;
70
+ border: 4px solid rgba(57, 182, 255, 1);
71
+ border-radius: 50%;
72
+ }
73
+ }
74
+
75
+ ._cursor_1dgwb_2._clicking_1dgwb_57 ._cursorRipple_1dgwb_39::after {
76
+ animation: _cursor-ripple_1dgwb_1 300ms ease-out forwards;
77
+ }
78
+
79
+ @keyframes _cursor-ripple_1dgwb_1 {
80
+ 0% {
81
+ transform: scale(0);
82
+ opacity: 1;
83
+ }
84
+ 100% {
85
+ transform: scale(2);
86
+ opacity: 0;
87
+ }
88
+ }`));
89
+ document.head.appendChild(elementStyle);
90
+ }
91
+ } catch (e) {
92
+ console.error("vite-plugin-css-injected-by-js", e);
93
+ }
94
+ })();
95
+ import { Motion } from "ai-motion";
96
+ function isPageDark() {
97
+ try {
98
+ if (hasDarkModeClass()) return true;
99
+ if (hasDarkModeDataAttribute()) return true;
100
+ if (isColorSchemeDark()) return true;
101
+ if (isBackgroundDark()) return true;
102
+ if (isMainContentBackgroundDark()) return true;
103
+ if (isTextColorLight()) return true;
104
+ return false;
105
+ } catch (error) {
106
+ console.warn("Error determining if page is dark:", error);
107
+ return false;
108
+ }
109
+ }
110
+ function hasDarkModeClass() {
111
+ const DEFAULT_DARK_MODE_CLASSES = ["dark", "dark-mode", "theme-dark", "night", "night-mode"];
112
+ const htmlElement = document.documentElement;
113
+ const bodyElement = document.body || document.documentElement;
114
+ for (const className of DEFAULT_DARK_MODE_CLASSES) {
115
+ if (htmlElement.classList.contains(className) || bodyElement?.classList.contains(className)) {
116
+ return true;
117
+ }
118
+ }
119
+ return false;
120
+ }
121
+ function hasDarkModeDataAttribute() {
122
+ const htmlElement = document.documentElement;
123
+ const bodyElement = document.body || document.documentElement;
124
+ const dataAttrs = ["data-theme", "data-color-mode", "data-bs-theme", "data-mui-color-scheme"];
125
+ for (const attr of dataAttrs) {
126
+ const bodyValue = bodyElement?.getAttribute(attr);
127
+ const htmlValue = htmlElement.getAttribute(attr);
128
+ if (bodyValue?.toLowerCase() === "dark" || htmlValue?.toLowerCase() === "dark") {
129
+ return true;
130
+ }
131
+ }
132
+ return false;
133
+ }
134
+ function isColorSchemeDark() {
135
+ const meta = document.querySelector('meta[name="color-scheme"]');
136
+ const metaContent = meta?.content.toLowerCase();
137
+ if (metaContent === "dark" || metaContent === "only dark") return true;
138
+ const rootStyle = window.getComputedStyle(document.documentElement);
139
+ const colorScheme = rootStyle.getPropertyValue("color-scheme").trim().toLowerCase();
140
+ return colorScheme === "dark" || colorScheme === "only dark";
141
+ }
142
+ function isBackgroundDark() {
143
+ const htmlStyle = window.getComputedStyle(document.documentElement);
144
+ const bodyStyle = window.getComputedStyle(document.body || document.documentElement);
145
+ const htmlBgColor = htmlStyle.backgroundColor;
146
+ const bodyBgColor = bodyStyle.backgroundColor;
147
+ if (isColorDark(bodyBgColor)) {
148
+ return true;
149
+ } else if (bodyBgColor === "transparent" || bodyBgColor.startsWith("rgba(0, 0, 0, 0)")) {
150
+ return isColorDark(htmlBgColor);
151
+ }
152
+ return false;
153
+ }
154
+ function isTextColorLight() {
155
+ const LIGHT_TEXT_LUMINANCE = 200;
156
+ const bodyStyle = window.getComputedStyle(document.body || document.documentElement);
157
+ const luminance = getLuminance(bodyStyle.color);
158
+ return luminance !== null && luminance > LIGHT_TEXT_LUMINANCE;
159
+ }
160
+ function isMainContentBackgroundDark() {
161
+ const { innerWidth: vw, innerHeight: vh } = window;
162
+ const minArea = vw * vh * 0.5;
163
+ const selectors = ["#app", "#root", "#__next"];
164
+ for (const selector of selectors) {
165
+ const el = document.querySelector(selector);
166
+ if (!el) continue;
167
+ const rect = el.getBoundingClientRect();
168
+ if (rect.width * rect.height < minArea) continue;
169
+ if (isColorDark(window.getComputedStyle(el).backgroundColor)) return true;
170
+ }
171
+ return false;
172
+ }
173
+ function parseRgbColor(colorString) {
174
+ const rgbMatch = /rgba?\((\d+),\s*(\d+),\s*(\d+)/.exec(colorString);
175
+ if (!rgbMatch) {
176
+ return null;
177
+ }
178
+ return {
179
+ r: parseInt(rgbMatch[1]),
180
+ g: parseInt(rgbMatch[2]),
181
+ b: parseInt(rgbMatch[3])
182
+ };
183
+ }
184
+ function getLuminance(colorString) {
185
+ if (!colorString || colorString === "transparent" || colorString.startsWith("rgba(0, 0, 0, 0)")) {
186
+ return null;
187
+ }
188
+ const rgb = parseRgbColor(colorString);
189
+ if (!rgb) {
190
+ return null;
191
+ }
192
+ return 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b;
193
+ }
194
+ function isColorDark(colorString, threshold = 128) {
195
+ const luminance = getLuminance(colorString);
196
+ return luminance !== null && luminance < threshold;
197
+ }
198
+ const wrapper = "_wrapper_1ooyb_1";
199
+ const visible = "_visible_1ooyb_11";
200
+ const styles = {
201
+ wrapper,
202
+ visible
203
+ };
204
+ const cursor = "_cursor_1dgwb_2";
205
+ const cursorBorder = "_cursorBorder_1dgwb_10";
206
+ const cursorFilling = "_cursorFilling_1dgwb_25";
207
+ const cursorRipple = "_cursorRipple_1dgwb_39";
208
+ const clicking = "_clicking_1dgwb_57";
209
+ const cursorStyles = {
210
+ cursor,
211
+ cursorBorder,
212
+ cursorFilling,
213
+ cursorRipple,
214
+ clicking
215
+ };
216
+ class SimulatorMask extends EventTarget {
217
+ shown = false;
218
+ wrapper = document.createElement("div");
219
+ motion = null;
220
+ #disposed = false;
221
+ #cursor = document.createElement("div");
222
+ #currentCursorX = 0;
223
+ #currentCursorY = 0;
224
+ #targetCursorX = 0;
225
+ #targetCursorY = 0;
226
+ constructor() {
227
+ super();
228
+ this.wrapper.id = "page-agent-runtime_simulator-mask";
229
+ this.wrapper.className = styles.wrapper;
230
+ this.wrapper.setAttribute("data-browser-use-ignore", "true");
231
+ this.wrapper.setAttribute("data-page-agent-ignore", "true");
232
+ try {
233
+ const motion = new Motion({
234
+ mode: isPageDark() ? "dark" : "light",
235
+ styles: { position: "absolute", inset: "0" }
236
+ });
237
+ this.motion = motion;
238
+ this.wrapper.appendChild(motion.element);
239
+ motion.autoResize(this.wrapper);
240
+ } catch (e) {
241
+ console.warn("[SimulatorMask] Motion overlay unavailable:", e);
242
+ }
243
+ this.wrapper.addEventListener("click", (e) => {
244
+ e.stopPropagation();
245
+ e.preventDefault();
246
+ });
247
+ this.wrapper.addEventListener("mousedown", (e) => {
248
+ e.stopPropagation();
249
+ e.preventDefault();
250
+ });
251
+ this.wrapper.addEventListener("mouseup", (e) => {
252
+ e.stopPropagation();
253
+ e.preventDefault();
254
+ });
255
+ this.wrapper.addEventListener("mousemove", (e) => {
256
+ e.stopPropagation();
257
+ e.preventDefault();
258
+ });
259
+ this.wrapper.addEventListener("wheel", (e) => {
260
+ e.stopPropagation();
261
+ e.preventDefault();
262
+ });
263
+ this.wrapper.addEventListener("keydown", (e) => {
264
+ e.stopPropagation();
265
+ e.preventDefault();
266
+ });
267
+ this.wrapper.addEventListener("keyup", (e) => {
268
+ e.stopPropagation();
269
+ e.preventDefault();
270
+ });
271
+ this.#createCursor();
272
+ document.body.appendChild(this.wrapper);
273
+ this.#moveCursorToTarget();
274
+ const movePointerToListener = (event) => {
275
+ const { x, y } = event.detail;
276
+ this.setCursorPosition(x, y);
277
+ };
278
+ const clickPointerListener = () => {
279
+ this.triggerClickAnimation();
280
+ };
281
+ const enablePassThroughListener = () => {
282
+ this.wrapper.style.pointerEvents = "none";
283
+ };
284
+ const disablePassThroughListener = () => {
285
+ this.wrapper.style.pointerEvents = "auto";
286
+ };
287
+ window.addEventListener("PageAgent::MovePointerTo", movePointerToListener);
288
+ window.addEventListener("PageAgent::ClickPointer", clickPointerListener);
289
+ window.addEventListener("PageAgent::EnablePassThrough", enablePassThroughListener);
290
+ window.addEventListener("PageAgent::DisablePassThrough", disablePassThroughListener);
291
+ this.addEventListener("dispose", () => {
292
+ window.removeEventListener("PageAgent::MovePointerTo", movePointerToListener);
293
+ window.removeEventListener("PageAgent::ClickPointer", clickPointerListener);
294
+ window.removeEventListener("PageAgent::EnablePassThrough", enablePassThroughListener);
295
+ window.removeEventListener("PageAgent::DisablePassThrough", disablePassThroughListener);
296
+ });
297
+ }
298
+ #createCursor() {
299
+ this.#cursor.className = cursorStyles.cursor;
300
+ const rippleContainer = document.createElement("div");
301
+ rippleContainer.className = cursorStyles.cursorRipple;
302
+ this.#cursor.appendChild(rippleContainer);
303
+ const fillingLayer = document.createElement("div");
304
+ fillingLayer.className = cursorStyles.cursorFilling;
305
+ this.#cursor.appendChild(fillingLayer);
306
+ const borderLayer = document.createElement("div");
307
+ borderLayer.className = cursorStyles.cursorBorder;
308
+ this.#cursor.appendChild(borderLayer);
309
+ this.wrapper.appendChild(this.#cursor);
310
+ }
311
+ #moveCursorToTarget() {
312
+ if (this.#disposed) return;
313
+ const newX = this.#currentCursorX + (this.#targetCursorX - this.#currentCursorX) * 0.2;
314
+ const newY = this.#currentCursorY + (this.#targetCursorY - this.#currentCursorY) * 0.2;
315
+ const xDistance = Math.abs(newX - this.#targetCursorX);
316
+ if (xDistance > 0) {
317
+ if (xDistance < 2) {
318
+ this.#currentCursorX = this.#targetCursorX;
319
+ } else {
320
+ this.#currentCursorX = newX;
321
+ }
322
+ this.#cursor.style.left = `${this.#currentCursorX}px`;
323
+ }
324
+ const yDistance = Math.abs(newY - this.#targetCursorY);
325
+ if (yDistance > 0) {
326
+ if (yDistance < 2) {
327
+ this.#currentCursorY = this.#targetCursorY;
328
+ } else {
329
+ this.#currentCursorY = newY;
330
+ }
331
+ this.#cursor.style.top = `${this.#currentCursorY}px`;
332
+ }
333
+ requestAnimationFrame(() => this.#moveCursorToTarget());
334
+ }
335
+ setCursorPosition(x, y) {
336
+ if (this.#disposed) return;
337
+ this.#targetCursorX = x;
338
+ this.#targetCursorY = y;
339
+ }
340
+ triggerClickAnimation() {
341
+ if (this.#disposed) return;
342
+ this.#cursor.classList.remove(cursorStyles.clicking);
343
+ this.#cursor.getBoundingClientRect();
344
+ this.#cursor.classList.add(cursorStyles.clicking);
345
+ }
346
+ show() {
347
+ if (this.shown || this.#disposed) return;
348
+ this.shown = true;
349
+ this.motion?.start();
350
+ this.motion?.fadeIn();
351
+ this.wrapper.classList.add(styles.visible);
352
+ this.#currentCursorX = window.innerWidth / 2;
353
+ this.#currentCursorY = window.innerHeight / 2;
354
+ this.#targetCursorX = this.#currentCursorX;
355
+ this.#targetCursorY = this.#currentCursorY;
356
+ this.#cursor.style.left = `${this.#currentCursorX}px`;
357
+ this.#cursor.style.top = `${this.#currentCursorY}px`;
358
+ }
359
+ hide() {
360
+ if (!this.shown || this.#disposed) return;
361
+ this.shown = false;
362
+ this.motion?.fadeOut();
363
+ this.motion?.pause();
364
+ this.#cursor.classList.remove(cursorStyles.clicking);
365
+ setTimeout(() => {
366
+ this.wrapper.classList.remove(styles.visible);
367
+ }, 800);
368
+ }
369
+ dispose() {
370
+ this.#disposed = true;
371
+ this.motion?.dispose();
372
+ this.wrapper.remove();
373
+ this.dispatchEvent(new Event("dispose"));
374
+ }
375
+ }
376
+ export {
377
+ SimulatorMask
378
+ };
379
+ //# sourceMappingURL=SimulatorMask-DIoKZt1z.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SimulatorMask-DIoKZt1z.js","sources":["../../vendor/page-agent/packages/page-controller/src/mask/checkDarkMode.ts","../../vendor/page-agent/packages/page-controller/src/mask/SimulatorMask.ts"],"sourcesContent":["/**\n * A comprehensive function to determine if the page is currently in a dark theme.\n * Heuristic check. Only work for common patterns. Return false by default.\n */\nexport function isPageDark() {\n\ttry {\n\t\tif (hasDarkModeClass()) return true\n\t\tif (hasDarkModeDataAttribute()) return true\n\t\tif (isColorSchemeDark()) return true\n\t\tif (isBackgroundDark()) return true\n\t\tif (isMainContentBackgroundDark()) return true\n\t\tif (isTextColorLight()) return true\n\n\t\treturn false\n\t} catch (error) {\n\t\tconsole.warn('Error determining if page is dark:', error)\n\t\treturn false\n\t}\n}\n\n/**\n * Checks for common dark mode CSS classes on the html or body elements.\n */\nfunction hasDarkModeClass() {\n\tconst DEFAULT_DARK_MODE_CLASSES = ['dark', 'dark-mode', 'theme-dark', 'night', 'night-mode']\n\n\tconst htmlElement = document.documentElement\n\tconst bodyElement = document.body || document.documentElement // can be null in some cases\n\n\t// Check class names on <html> and <body>\n\tfor (const className of DEFAULT_DARK_MODE_CLASSES) {\n\t\tif (htmlElement.classList.contains(className) || bodyElement?.classList.contains(className)) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n/**\n * Some UI frameworks use data attributes to indicate theme\n */\nfunction hasDarkModeDataAttribute() {\n\tconst htmlElement = document.documentElement\n\tconst bodyElement = document.body || document.documentElement // can be null in some cases\n\n\tconst dataAttrs = ['data-theme', 'data-color-mode', 'data-bs-theme', 'data-mui-color-scheme']\n\tfor (const attr of dataAttrs) {\n\t\tconst bodyValue = bodyElement?.getAttribute(attr)\n\t\tconst htmlValue = htmlElement.getAttribute(attr)\n\n\t\tif (bodyValue?.toLowerCase() === 'dark' || htmlValue?.toLowerCase() === 'dark') {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n/**\n * Checks the CSS `color-scheme` property and `<meta name=\"color-scheme\">` tag.\n * Only \"dark\"/\"only dark\" counts as dark; \"light dark\" is ambiguous and ignored.\n */\nfunction isColorSchemeDark() {\n\t// Check <meta name=\"color-scheme\" content=\"dark\">\n\tconst meta = document.querySelector<HTMLMetaElement>('meta[name=\"color-scheme\"]')\n\tconst metaContent = meta?.content.toLowerCase()\n\tif (metaContent === 'dark' || metaContent === 'only dark') return true\n\n\t// Check the computed color-scheme CSS property on :root\n\tconst rootStyle = window.getComputedStyle(document.documentElement)\n\tconst colorScheme = rootStyle.getPropertyValue('color-scheme').trim().toLowerCase()\n\treturn colorScheme === 'dark' || colorScheme === 'only dark'\n}\n\n/**\n * Checks the background color of the body element to determine if the page is dark.\n */\nfunction isBackgroundDark() {\n\t// We check both <html> and <body> because some pages set the color on <html>\n\tconst htmlStyle = window.getComputedStyle(document.documentElement)\n\tconst bodyStyle = window.getComputedStyle(document.body || document.documentElement)\n\n\t// Get background colors\n\tconst htmlBgColor = htmlStyle.backgroundColor\n\tconst bodyBgColor = bodyStyle.backgroundColor\n\n\t// The body's background might be transparent, in which case we should\n\t// fall back to the html element's background.\n\tif (isColorDark(bodyBgColor)) {\n\t\treturn true\n\t} else if (bodyBgColor === 'transparent' || bodyBgColor.startsWith('rgba(0, 0, 0, 0)')) {\n\t\treturn isColorDark(htmlBgColor)\n\t}\n\n\treturn false\n}\n\n/**\n * Checks if the text color on the body is light, which implies a dark background.\n */\nfunction isTextColorLight() {\n\t/** Luminance (0-255) above which body text is considered light */\n\tconst LIGHT_TEXT_LUMINANCE = 200\n\n\tconst bodyStyle = window.getComputedStyle(document.body || document.documentElement)\n\tconst luminance = getLuminance(bodyStyle.color)\n\n\t// Light text has high luminance (e.g. white text on dark bg)\n\treturn luminance !== null && luminance > LIGHT_TEXT_LUMINANCE\n}\n\n/**\n * Checks the background color of major layout elements (#app, #root, etc.).\n * Many SPAs render into a container that may have its own dark background while\n * <body> remains transparent.\n */\nfunction isMainContentBackgroundDark() {\n\tconst { innerWidth: vw, innerHeight: vh } = window\n\tconst minArea = vw * vh * 0.5\n\n\tconst selectors = ['#app', '#root', '#__next']\n\tfor (const selector of selectors) {\n\t\tconst el = document.querySelector(selector)\n\t\tif (!el) continue\n\n\t\tconst rect = el.getBoundingClientRect()\n\t\tif (rect.width * rect.height < minArea) continue\n\n\t\tif (isColorDark(window.getComputedStyle(el).backgroundColor)) return true\n\t}\n\treturn false\n}\n\n// --- utils ---\n\n/**\n * Parses an RGB or RGBA color string and returns an object with r, g, b properties.\n * @param {string} colorString - e.g., \"rgb(34, 34, 34)\" or \"rgba(0, 0, 0, 0.5)\"\n * @returns {{r: number, g: number, b: number}|null}\n */\nfunction parseRgbColor(colorString: string) {\n\tconst rgbMatch = /rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/.exec(colorString)\n\tif (!rgbMatch) {\n\t\treturn null // Not a valid rgb/rgba string\n\t}\n\treturn {\n\t\tr: parseInt(rgbMatch[1]),\n\t\tg: parseInt(rgbMatch[2]),\n\t\tb: parseInt(rgbMatch[3]),\n\t}\n}\n\n/**\n * Calculates the perceived luminance (0-255) of a CSS color string.\n * @param {string} colorString - e.g., \"rgb(50, 50, 50)\" or \"rgba(0, 0, 0, 0.5)\"\n * @returns {number|null} - The luminance, or null if the color is transparent or unparseable.\n */\nfunction getLuminance(colorString: string): number | null {\n\tif (!colorString || colorString === 'transparent' || colorString.startsWith('rgba(0, 0, 0, 0)')) {\n\t\treturn null // Transparent has no meaningful luminance\n\t}\n\n\tconst rgb = parseRgbColor(colorString)\n\tif (!rgb) {\n\t\treturn null // Could not parse color\n\t}\n\n\t// Standard perceived luminance formula\n\treturn 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b\n}\n\n/**\n * Determines if a color is \"dark\" based on its calculated luminance.\n * @param {string} colorString - The CSS color string (e.g., \"rgb(50, 50, 50)\").\n * @param {number} threshold - A value between 0 and 255. Colors with luminance below this will be considered dark. Default is 128.\n */\nfunction isColorDark(colorString: string, threshold = 128) {\n\tconst luminance = getLuminance(colorString)\n\treturn luminance !== null && luminance < threshold\n}\n","import { Motion } from 'ai-motion'\n\nimport { isPageDark } from './checkDarkMode'\n\nimport styles from './SimulatorMask.module.css'\nimport cursorStyles from './cursor.module.css'\n\nexport class SimulatorMask extends EventTarget {\n\tshown: boolean = false\n\twrapper = document.createElement('div')\n\tmotion: Motion | null = null\n\n\t#disposed = false\n\n\t#cursor = document.createElement('div')\n\n\t#currentCursorX = 0\n\t#currentCursorY = 0\n\n\t#targetCursorX = 0\n\t#targetCursorY = 0\n\n\tconstructor() {\n\t\tsuper()\n\n\t\tthis.wrapper.id = 'page-agent-runtime_simulator-mask'\n\t\tthis.wrapper.className = styles.wrapper\n\t\tthis.wrapper.setAttribute('data-browser-use-ignore', 'true')\n\t\tthis.wrapper.setAttribute('data-page-agent-ignore', 'true')\n\n\t\ttry {\n\t\t\tconst motion = new Motion({\n\t\t\t\tmode: isPageDark() ? 'dark' : 'light',\n\t\t\t\tstyles: { position: 'absolute', inset: '0' },\n\t\t\t})\n\t\t\tthis.motion = motion\n\t\t\tthis.wrapper.appendChild(motion.element)\n\t\t\tmotion.autoResize(this.wrapper)\n\t\t} catch (e) {\n\t\t\tconsole.warn('[SimulatorMask] Motion overlay unavailable:', e)\n\t\t}\n\n\t\t// Capture all mouse, keyboard, and wheel events\n\t\tthis.wrapper.addEventListener('click', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\t\tthis.wrapper.addEventListener('mousedown', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\t\tthis.wrapper.addEventListener('mouseup', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\t\tthis.wrapper.addEventListener('mousemove', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\t\tthis.wrapper.addEventListener('wheel', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\t\tthis.wrapper.addEventListener('keydown', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\t\tthis.wrapper.addEventListener('keyup', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\n\t\t// Create AI cursor\n\t\tthis.#createCursor()\n\t\t// this.show()\n\n\t\tdocument.body.appendChild(this.wrapper)\n\n\t\tthis.#moveCursorToTarget()\n\n\t\t// global events\n\t\t// @note Mask should be isolated from the rest of the code.\n\t\t// Global events are easier to manage and cleanup.\n\n\t\tconst movePointerToListener = (event: Event) => {\n\t\t\tconst { x, y } = (event as CustomEvent).detail\n\t\t\tthis.setCursorPosition(x, y)\n\t\t}\n\t\tconst clickPointerListener = () => {\n\t\t\tthis.triggerClickAnimation()\n\t\t}\n\t\tconst enablePassThroughListener = () => {\n\t\t\tthis.wrapper.style.pointerEvents = 'none'\n\t\t}\n\t\tconst disablePassThroughListener = () => {\n\t\t\tthis.wrapper.style.pointerEvents = 'auto'\n\t\t}\n\n\t\twindow.addEventListener('PageAgent::MovePointerTo', movePointerToListener)\n\t\twindow.addEventListener('PageAgent::ClickPointer', clickPointerListener)\n\t\twindow.addEventListener('PageAgent::EnablePassThrough', enablePassThroughListener)\n\t\twindow.addEventListener('PageAgent::DisablePassThrough', disablePassThroughListener)\n\n\t\tthis.addEventListener('dispose', () => {\n\t\t\twindow.removeEventListener('PageAgent::MovePointerTo', movePointerToListener)\n\t\t\twindow.removeEventListener('PageAgent::ClickPointer', clickPointerListener)\n\t\t\twindow.removeEventListener('PageAgent::EnablePassThrough', enablePassThroughListener)\n\t\t\twindow.removeEventListener('PageAgent::DisablePassThrough', disablePassThroughListener)\n\t\t})\n\t}\n\n\t#createCursor() {\n\t\tthis.#cursor.className = cursorStyles.cursor\n\n\t\t// Create ripple effect container\n\t\tconst rippleContainer = document.createElement('div')\n\t\trippleContainer.className = cursorStyles.cursorRipple\n\t\tthis.#cursor.appendChild(rippleContainer)\n\n\t\t// Create filling layer\n\t\tconst fillingLayer = document.createElement('div')\n\t\tfillingLayer.className = cursorStyles.cursorFilling\n\t\tthis.#cursor.appendChild(fillingLayer)\n\n\t\t// Create border layer\n\t\tconst borderLayer = document.createElement('div')\n\t\tborderLayer.className = cursorStyles.cursorBorder\n\t\tthis.#cursor.appendChild(borderLayer)\n\n\t\tthis.wrapper.appendChild(this.#cursor)\n\t}\n\n\t#moveCursorToTarget() {\n\t\tif (this.#disposed) return\n\n\t\tconst newX = this.#currentCursorX + (this.#targetCursorX - this.#currentCursorX) * 0.2\n\t\tconst newY = this.#currentCursorY + (this.#targetCursorY - this.#currentCursorY) * 0.2\n\n\t\tconst xDistance = Math.abs(newX - this.#targetCursorX)\n\t\tif (xDistance > 0) {\n\t\t\tif (xDistance < 2) {\n\t\t\t\tthis.#currentCursorX = this.#targetCursorX\n\t\t\t} else {\n\t\t\t\tthis.#currentCursorX = newX\n\t\t\t}\n\t\t\tthis.#cursor.style.left = `${this.#currentCursorX}px`\n\t\t}\n\n\t\tconst yDistance = Math.abs(newY - this.#targetCursorY)\n\t\tif (yDistance > 0) {\n\t\t\tif (yDistance < 2) {\n\t\t\t\tthis.#currentCursorY = this.#targetCursorY\n\t\t\t} else {\n\t\t\t\tthis.#currentCursorY = newY\n\t\t\t}\n\t\t\tthis.#cursor.style.top = `${this.#currentCursorY}px`\n\t\t}\n\n\t\trequestAnimationFrame(() => this.#moveCursorToTarget())\n\t}\n\n\tsetCursorPosition(x: number, y: number) {\n\t\tif (this.#disposed) return\n\n\t\tthis.#targetCursorX = x\n\t\tthis.#targetCursorY = y\n\t}\n\n\ttriggerClickAnimation() {\n\t\tif (this.#disposed) return\n\n\t\tthis.#cursor.classList.remove(cursorStyles.clicking)\n\t\t// Force reflow to restart animation\n\t\tthis.#cursor.getBoundingClientRect()\n\t\tthis.#cursor.classList.add(cursorStyles.clicking)\n\t}\n\n\tshow() {\n\t\tif (this.shown || this.#disposed) return\n\n\t\tthis.shown = true\n\t\tthis.motion?.start()\n\t\tthis.motion?.fadeIn()\n\n\t\tthis.wrapper.classList.add(styles.visible)\n\n\t\t// Initialize cursor position\n\t\tthis.#currentCursorX = window.innerWidth / 2\n\t\tthis.#currentCursorY = window.innerHeight / 2\n\t\tthis.#targetCursorX = this.#currentCursorX\n\t\tthis.#targetCursorY = this.#currentCursorY\n\t\tthis.#cursor.style.left = `${this.#currentCursorX}px`\n\t\tthis.#cursor.style.top = `${this.#currentCursorY}px`\n\t}\n\n\thide() {\n\t\tif (!this.shown || this.#disposed) return\n\n\t\tthis.shown = false\n\t\tthis.motion?.fadeOut()\n\t\tthis.motion?.pause()\n\n\t\tthis.#cursor.classList.remove(cursorStyles.clicking)\n\n\t\tsetTimeout(() => {\n\t\t\tthis.wrapper.classList.remove(styles.visible)\n\t\t}, 800) // Match the animation duration\n\t}\n\n\tdispose() {\n\t\tthis.#disposed = true\n\t\tthis.motion?.dispose()\n\t\tthis.wrapper.remove()\n\t\tthis.dispatchEvent(new Event('dispose'))\n\t}\n}\n"],"names":[],"mappings":"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"}