@diegosouzacdv/jev-browser-mcp 0.3.0 → 0.4.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/README.md +327 -1771
- package/config/ui-testing.json +15 -3
- package/docs/jev-browser-mcp.md +61 -37
- package/mcp_servers/jev-browser-npm/bin/jev-browser-mcp.cjs +1 -1
- package/mcp_servers/jev-browser-npm/src/config.mjs +49 -8
- package/mcp_servers/jev-browser-npm/src/flow.mjs +548 -228
- package/mcp_servers/jev-browser-npm/src/server.mjs +8 -12
- package/package.json +32 -35
package/README.md
CHANGED
|
@@ -1,1818 +1,374 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Automação de tela com Jev e Playwright
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
persistência SQLite, revisão multiagente, gates determinísticos, HITL e
|
|
5
|
-
artefatos versionados por manifesto.
|
|
3
|
+
## Instalar em outros harnesses com npm/npx
|
|
6
4
|
|
|
7
|
-
O
|
|
8
|
-
|
|
9
|
-
`
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
O repositório também contém um pacote Node independente do harness. Ele fala
|
|
6
|
+
MCP por `stdio`, executa o Playwright no mesmo processo e pode ser iniciado por
|
|
7
|
+
qualquer harness que aceite `command` e `args` para um servidor MCP. O pacote
|
|
8
|
+
usa o mesmo `config/ui-testing.json` deste repositório; não precisa instalar o
|
|
9
|
+
Python do orquestrador. As opções específicas do pacote ficam isoladas no
|
|
10
|
+
bloco `jev_browser_mcp` para preservar o contrato do servidor Python.
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
skills, MCP e ownership). Valide ou projete as variáveis de um workflow com:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
python scripts/validate_agent_policy.py validate
|
|
19
|
-
python scripts/render_agent_policy.py show
|
|
20
|
-
python scripts/render_agent_policy.py --github-env
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
`ORQUESTRADOR_AGENT_RUNTIME_JSON` e demais variáveis legadas existem apenas
|
|
24
|
-
para compatibilidade explícita; os workflows canônicos derivam a configuração
|
|
25
|
-
do arquivo central.
|
|
26
|
-
|
|
27
|
-
## 1. Pré-requisitos
|
|
28
|
-
|
|
29
|
-
- Python 3.12+;
|
|
30
|
-
- Git;
|
|
31
|
-
- Docker Desktop/Engine para o sandbox estrito e para o Gitea local;
|
|
32
|
-
- Node.js/npm somente quando houver validação TypeScript;
|
|
33
|
-
- uma chave Groq criada no [console de API da Groq](https://console.groq.com/keys)
|
|
34
|
-
ou uma chave OpenRouter criada em [OpenRouter Keys](https://openrouter.ai/settings/keys);
|
|
35
|
-
- OpenSSH no Windows para o acesso ao Gitea;
|
|
36
|
-
- portas locais `3000` (HTTP) e `2222` (SSH) livres.
|
|
37
|
-
|
|
38
|
-
Se uma chave foi publicada em chat, commit ou log, revogue-a e crie outra antes
|
|
39
|
-
de continuar.
|
|
40
|
-
|
|
41
|
-
## 2. Instalação local
|
|
42
|
-
|
|
43
|
-
Windows PowerShell:
|
|
44
|
-
|
|
45
|
-
```powershell
|
|
46
|
-
py -3.12 -m venv .venv
|
|
47
|
-
.venv\Scripts\Activate.ps1
|
|
48
|
-
python -m pip install --upgrade pip
|
|
49
|
-
pip install -r requirements.txt
|
|
50
|
-
Copy-Item .env.example .env
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Linux/macOS:
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
python3.12 -m venv .venv
|
|
57
|
-
source .venv/bin/activate
|
|
58
|
-
python -m pip install --upgrade pip
|
|
59
|
-
pip install -r requirements.txt
|
|
60
|
-
cp .env.example .env
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Edite `.env` localmente. O arquivo `.env.example` é o catálogo versionado das
|
|
64
|
-
variáveis suportadas; o `.env` é ignorado pelo Git e contém os valores
|
|
65
|
-
específicos da máquina. Para o perfil local deste projeto:
|
|
66
|
-
|
|
67
|
-
```dotenv
|
|
68
|
-
ORQUESTRADOR_LLM_PROVIDER=openrouter
|
|
69
|
-
OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
|
|
70
|
-
ORQUESTRADOR_AGENT_MODEL=deepseek/deepseek-v4-flash-0731
|
|
71
|
-
MCP_FILESYSTEM_ROOTS=.
|
|
72
|
-
ORQUESTRADOR_SANDBOX_MODE=docker
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Quando a chave estiver cadastrada no ambiente de usuário do Windows, use a
|
|
76
|
-
expansão `${OPENROUTER_API_KEY}` acima; não copie o segredo para o `.env`. Para
|
|
77
|
-
CI e produção, use secrets/variáveis do ambiente; não publique o arquivo `.env`.
|
|
78
|
-
|
|
79
|
-
### Conferir a integridade da `.venv`
|
|
80
|
-
|
|
81
|
-
A `.venv` é ignorada pelo Git: se algo reescrever um arquivo dela, nada o traz de
|
|
82
|
-
volta. Em 2026-08-28 um script de tradução reescreveu 23 pacotes (`resolver` →
|
|
83
|
-
`resolve`), e isso só apareceu semanas depois, como erros sem relação aparente.
|
|
84
|
-
Cada pacote instalado guarda o sha256 de cada arquivo no próprio `RECORD`, e o
|
|
85
|
-
script abaixo confere tudo contra ele:
|
|
86
|
-
|
|
87
|
-
```powershell
|
|
88
|
-
.venv\Scripts\python.exe scripts\venv_integrity.py
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
.venv/bin/python scripts/venv_integrity.py
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Sai `0` com `"clean": true`; sai `1` nomeando pacote e arquivo em `mismatched`
|
|
96
|
-
(conteúdo diferente), `missing` (arquivo apagado) ou `unverifiable` (pacote sem
|
|
97
|
-
`RECORD`, que não dá para conferir e por isso não conta como limpo). Rode com o
|
|
98
|
-
interpretador da `.venv` que quer conferir. Leva de 1 a 11 s, conforme o cache de
|
|
99
|
-
disco. Para reparar um pacote apontado:
|
|
100
|
-
`pip install --force-reinstall --no-deps <pacote>==<versão>`.
|
|
101
|
-
|
|
102
|
-
Na agenda do Hermes, uma vez por semana, pela skill
|
|
103
|
-
`.hermes/skills/orquestrador-venv-integrity/`, que só reporta e nunca repara; o
|
|
104
|
-
comando `hermes cron create` está no `SKILL.md` dela.
|
|
105
|
-
|
|
106
|
-
## 2.1. Gitea local para Actions e espelhamento do GitHub
|
|
107
|
-
|
|
108
|
-
O GitHub é a origem canônica deste projeto. O Gitea é uma cópia local usada para
|
|
109
|
-
executar os workflows self-hosted; portanto, o fluxo normal é publicar primeiro
|
|
110
|
-
em `origin` (GitHub) e depois atualizar o remote `gitea`. Não trate o clone local
|
|
111
|
-
do Gitea como fonte de verdade.
|
|
112
|
-
|
|
113
|
-
| Componente | Caminho/URL nesta máquina |
|
|
114
|
-
|---|---|
|
|
115
|
-
| Checkout de desenvolvimento | `C:\orquestrador` |
|
|
116
|
-
| Servidor Gitea, compose e dados persistentes | `C:\gitea` |
|
|
117
|
-
| Runner Linux/Docker | `C:\gitea-runner` |
|
|
118
|
-
| Runner Windows/host | `C:\gitea-runner-win` |
|
|
119
|
-
| URL configurada no compose | `http://192.168.1.3:3000` |
|
|
120
|
-
| SSH do Gitea | `ssh://gitea-local/...` na porta `2222` |
|
|
121
|
-
|
|
122
|
-
O `docker-compose.yml` atual usa `192.168.1.3`. Se o endereço da máquina mudar, atualize
|
|
123
|
-
`ROOT_URL`, `DOMAIN` e `SSH_DOMAIN` no compose antes de registrar novos runners.
|
|
124
|
-
Não misture `localhost`, o IP da LAN e outro hostname no mesmo registro: o endereço usado
|
|
125
|
-
pelo runner precisa ser alcançável por ele.
|
|
126
|
-
|
|
127
|
-
Referências oficiais: [instalação com Docker](https://docs.gitea.com/next/installation/install-with-docker/),
|
|
128
|
-
[Gitea Actions](https://docs.gitea.com/usage/actions/overview/) e
|
|
129
|
-
[Gitea Runner](https://docs.gitea.com/usage/actions/act-runner/).
|
|
130
|
-
|
|
131
|
-
### Subir o servidor Gitea
|
|
132
|
-
|
|
133
|
-
Inicie o Docker Desktop com o engine Linux e confirme que ele está saudável:
|
|
134
|
-
|
|
135
|
-
~~~powershell
|
|
136
|
-
docker info
|
|
137
|
-
docker compose version
|
|
138
|
-
~~~
|
|
139
|
-
|
|
140
|
-
Suba a instância já configurada em `C:\gitea`; não recrie o volume de dados:
|
|
141
|
-
|
|
142
|
-
~~~powershell
|
|
143
|
-
Set-Location C:\gitea
|
|
144
|
-
docker compose pull
|
|
145
|
-
docker compose up -d
|
|
146
|
-
docker compose ps
|
|
147
|
-
docker compose logs --tail 100 server
|
|
148
|
-
Invoke-WebRequest http://192.168.1.3:3000/api/v1/version -TimeoutSec 10
|
|
149
|
-
~~~
|
|
150
|
-
|
|
151
|
-
Na primeira instalação, acesse <http://192.168.1.3:3000>, crie o usuário
|
|
152
|
-
administrador, ative Actions e confirme que o repositório
|
|
153
|
-
`admin/projeto-global` existe. Os dados ficam em `C:\gitea\gitea`; não use
|
|
154
|
-
`docker compose down -v`, pois isso remove o armazenamento persistente.
|
|
155
|
-
|
|
156
|
-
Para parar temporariamente sem apagar dados:
|
|
157
|
-
|
|
158
|
-
~~~powershell
|
|
159
|
-
Set-Location C:\gitea
|
|
160
|
-
docker compose stop
|
|
161
|
-
~~~
|
|
162
|
-
|
|
163
|
-
### GitHub como origem e Gitea como espelho
|
|
164
|
-
|
|
165
|
-
No checkout principal, preserve `origin` apontando para o GitHub e mantenha o
|
|
166
|
-
Gitea em um segundo remote:
|
|
167
|
-
|
|
168
|
-
~~~powershell
|
|
169
|
-
Set-Location C:\orquestrador
|
|
170
|
-
git remote -v
|
|
171
|
-
git remote set-url origin https://github.com/diegosouzacdv/orquestrador.git
|
|
172
|
-
git remote set-url gitea ssh://gitea-local/admin/projeto-global.git
|
|
173
|
-
git fetch origin --prune
|
|
174
|
-
~~~
|
|
175
|
-
|
|
176
|
-
Se `gitea` ainda não existir, use `git remote add gitea ...` em vez de
|
|
177
|
-
`set-url`. A chave SSH do Gitea pode ser criada assim:
|
|
178
|
-
|
|
179
|
-
~~~powershell
|
|
180
|
-
$sshDir = Join-Path $env:USERPROFILE ".ssh"
|
|
181
|
-
New-Item -ItemType Directory -Force $sshDir | Out-Null
|
|
182
|
-
ssh-keygen -t ed25519 -C "gitea-local" -f (Join-Path $sshDir "gitea_local")
|
|
183
|
-
Get-Content (Join-Path $sshDir "gitea_local.pub")
|
|
184
|
-
~~~
|
|
185
|
-
|
|
186
|
-
Adicione a chave pública em **Gitea → Settings → SSH / GPG Keys** e crie
|
|
187
|
-
`C:\Users\<usuario>\.ssh\config`:
|
|
188
|
-
|
|
189
|
-
~~~sshconfig
|
|
190
|
-
Host gitea-local
|
|
191
|
-
HostName 192.168.1.3
|
|
192
|
-
Port 2222
|
|
193
|
-
User git
|
|
194
|
-
IdentityFile ~/.ssh/gitea_local
|
|
195
|
-
IdentitiesOnly yes
|
|
196
|
-
~~~
|
|
197
|
-
|
|
198
|
-
Valide a autenticação e publique o mesmo commit nos dois destinos:
|
|
199
|
-
|
|
200
|
-
~~~powershell
|
|
201
|
-
ssh -T gitea-local
|
|
202
|
-
git pull --ff-only origin master
|
|
203
|
-
git push origin master
|
|
204
|
-
git push gitea master:master
|
|
205
|
-
git push gitea --tags
|
|
206
|
-
git ls-remote origin refs/heads/master
|
|
207
|
-
git ls-remote gitea refs/heads/master
|
|
208
|
-
~~~
|
|
209
|
-
|
|
210
|
-
O `ssh -T` pode responder que não existe shell interativo; isso é esperado se a
|
|
211
|
-
autenticação funcionar. O último bloco deve mostrar o mesmo SHA no GitHub e
|
|
212
|
-
no Gitea. O clone atual em `C:\gitea-runner\repositorio` é apenas um bootstrap
|
|
213
|
-
antigo e não deve ser usado como origem do espelho sem antes conferir seus
|
|
214
|
-
remotes e seu estado:
|
|
215
|
-
|
|
216
|
-
~~~powershell
|
|
217
|
-
git -C C:\gitea-runner\repositorio remote -v
|
|
218
|
-
git -C C:\gitea-runner\repositorio status --short --branch
|
|
219
|
-
~~~
|
|
220
|
-
|
|
221
|
-
Para espelhar também branches e tags removidas, use um clone bare dedicado.
|
|
222
|
-
O comando `git push --mirror` substitui todas as refs do repositório de destino;
|
|
223
|
-
confirme o destino antes de executá-lo e não aponte para um repositório com
|
|
224
|
-
trabalho independente:
|
|
225
|
-
|
|
226
|
-
~~~powershell
|
|
227
|
-
$mirror = "C:\gitea-runner\github-mirror.git"
|
|
228
|
-
if (Test-Path $mirror) { throw "O mirror já existe; valide-o manualmente antes de reutilizar o caminho." }
|
|
229
|
-
git clone --mirror https://github.com/diegosouzacdv/orquestrador.git $mirror
|
|
230
|
-
Set-Location $mirror
|
|
231
|
-
git remote add gitea ssh://gitea-local/admin/projeto-global.git
|
|
232
|
-
git fetch origin --prune
|
|
233
|
-
git push --mirror gitea
|
|
234
|
-
~~~
|
|
235
|
-
|
|
236
|
-
### Runner Linux/Docker — `C:\gitea-runner`
|
|
237
|
-
|
|
238
|
-
Este é o runner usado pelos workflows com `runs-on: ubuntu-latest`, como
|
|
239
|
-
`.gitea/workflows/agentic-graph-e2e.yml` e
|
|
240
|
-
`.gitea/workflows/mcp-integration-smoke.yml`. Ele usa Docker Desktop e o
|
|
241
|
-
socket `/var/run/docker.sock` dentro dos jobs. O socket dá ao job controle do
|
|
242
|
-
Docker do host; aceite jobs somente do repositório confiável.
|
|
243
|
-
|
|
244
|
-
O binário e a configuração já ficam em `C:\gitea-runner`. Se for necessário
|
|
245
|
-
registrar novamente, gere o arquivo uma única vez, solicite um token novo na
|
|
246
|
-
interface do Gitea e não salve o token em arquivo, histórico ou Git:
|
|
247
|
-
|
|
248
|
-
~~~powershell
|
|
249
|
-
Set-Location C:\gitea-runner
|
|
250
|
-
if (!(Test-Path .\config.yaml)) {
|
|
251
|
-
.\act_runner.exe config generate | Out-File -Encoding utf8 .\config.yaml
|
|
252
|
-
}
|
|
253
|
-
$runnerToken = Read-Host "Token do runner Gitea"
|
|
254
|
-
.\act_runner.exe register --no-interactive --instance "http://192.168.1.3:3000" --token $runnerToken --name "orquestrador-local" --labels "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest,ubuntu-24.04:docker://docker.gitea.com/runner-images:ubuntu-24.04,ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04"
|
|
255
|
-
Remove-Variable runnerToken
|
|
256
|
-
~~~
|
|
257
|
-
|
|
258
|
-
Na configuração, preserve pelo menos estas opções para os workflows Docker:
|
|
259
|
-
|
|
260
|
-
~~~yaml
|
|
261
|
-
container:
|
|
262
|
-
options: --volume /var/run/docker.sock:/var/run/docker.sock --add-host=host.docker.internal:host-gateway
|
|
263
|
-
valid_volumes:
|
|
264
|
-
- '**'
|
|
265
|
-
require_docker: true
|
|
266
|
-
docker_timeout: 60s
|
|
267
|
-
bind_workdir: true
|
|
268
|
-
~~~
|
|
269
|
-
|
|
270
|
-
Teste em primeiro plano antes de depender do serviço do Windows:
|
|
271
|
-
|
|
272
|
-
~~~powershell
|
|
273
|
-
docker info
|
|
274
|
-
Set-Location C:\gitea-runner
|
|
275
|
-
.\act_runner.exe daemon --config C:\gitea-runner\config.yaml
|
|
276
|
-
~~~
|
|
277
|
-
|
|
278
|
-
Nesta máquina, o serviço `GiteaActRunner` já está configurado para executar
|
|
279
|
-
`C:\gitea-runner\act_runner.exe daemon --config C:\gitea-runner\config.yaml`.
|
|
280
|
-
Depois que o Docker Desktop estiver pronto, reinicie-o e confira o log:
|
|
281
|
-
|
|
282
|
-
~~~powershell
|
|
283
|
-
Get-Service GiteaActRunner
|
|
284
|
-
Restart-Service GiteaActRunner
|
|
285
|
-
Get-Content C:\gitea-runner\service.err.log -Tail 80
|
|
286
|
-
~~~
|
|
287
|
-
|
|
288
|
-
O serviço deve aparecer como `Running` e o runner deve ficar online em
|
|
289
|
-
**Gitea → Actions → Runners**. Se `docker info` falhar ou o log mencionar
|
|
290
|
-
`Docker Engine socket not found`, corrija o Docker Desktop primeiro; reiniciar
|
|
291
|
-
o runner sozinho não resolve essa causa.
|
|
292
|
-
|
|
293
|
-
Estado verificado em 2026-09-13: serviço `Running`, runner `orquestrador-local`
|
|
294
|
-
(id 3) em `v3.3.1`, com as três labels `ubuntu-*` e `last_online` corrente. O
|
|
295
|
-
log de sucesso tem exatamente três linhas — `Starting runner daemon`,
|
|
296
|
-
`Docker is ready` e `declare successfully`. A ausência de `Docker is ready`
|
|
297
|
-
neste runner é defeito; no runner Windows, não.
|
|
298
|
-
|
|
299
|
-
Uma enxurrada de `failed to fetch task ... target machine actively refused it`
|
|
300
|
-
**não** é defeito do runner: é o container do Gitea parado. O runner fica em
|
|
301
|
-
retry e volta sozinho quando o servidor sobe — foi o que ocorreu entre 07:41 e
|
|
302
|
-
07:55 de 2026-09-13, com recuperação às 07:59 sem nenhuma intervenção no
|
|
303
|
-
runner. Antes de mexer no runner, confira `docker compose ps` em `C:\gitea`.
|
|
304
|
-
|
|
305
|
-
### Runner Windows/host — `C:\gitea-runner-win`
|
|
306
|
-
|
|
307
|
-
Este é um segundo runner, independente do runner Docker, registrado como
|
|
308
|
-
`orquestrador-windows` (id 4) com a label `windows-latest:host`. Ele é usado
|
|
309
|
-
pelos jobs Windows da matriz `os: [ubuntu-latest, windows-latest]` de
|
|
310
|
-
`.gitea/workflows/antigravity-sdk-homologation.yml` — hoje o único workflow
|
|
311
|
-
Gitea que não é só `ubuntu-latest`. Sem este runner, a perna `windows-latest`
|
|
312
|
-
dessa matriz fica enfileirada para sempre em vez de falhar, que é o sintoma
|
|
313
|
-
pelo qual essa ausência costuma ser percebida.
|
|
314
|
-
|
|
315
|
-
**O serviço `GiteaActRunnerWindows` já existe nesta máquina** e está correto:
|
|
316
|
-
NSSM, `AUTO_START`, `Application=C:\gitea-runner-win\act_runner.exe`,
|
|
317
|
-
`AppDirectory=C:\gitea-runner-win`,
|
|
318
|
-
`AppParameters=daemon --config C:\gitea-runner-win\config.yaml`. Confira antes
|
|
319
|
-
de tentar criá-lo de novo — `nssm install` sobre um serviço existente não é o
|
|
320
|
-
caminho:
|
|
321
|
-
|
|
322
|
-
~~~powershell
|
|
323
|
-
sc.exe query GiteaActRunnerWindows
|
|
324
|
-
reg query "HKLM\SYSTEM\CurrentControlSet\Services\GiteaActRunnerWindows\Parameters"
|
|
325
|
-
~~~
|
|
326
|
-
|
|
327
|
-
#### As duas causas que mantiveram este runner parado
|
|
328
|
-
|
|
329
|
-
Diagnosticadas em 2026-09-13, com o serviço existente e bem configurado:
|
|
330
|
-
|
|
331
|
-
1. **O binário não estava lá.** `C:\gitea-runner-win\` tinha `.runner` e
|
|
332
|
-
`config.yaml`, mas não `act_runner.exe`. O serviço sobe, não acha o
|
|
333
|
-
executável e morre com `ESTADO: STOPPED`, `WIN32_EXIT_CODE: 1066 (0x42a)` e
|
|
334
|
-
`SERVICE_EXIT_CODE: 3` — `ERROR_PATH_NOT_FOUND`. O 1066 é genérico
|
|
335
|
-
("erro específico do serviço") e não aponta o arquivo; quem lê só o 1066
|
|
336
|
-
procura defeito de registro e não encontra nada.
|
|
337
|
-
2. **`require_docker: true` herdado do runner Docker.** O `config.yaml` deste
|
|
338
|
-
diretório é cópia do config do runner Linux, inclusive do bloco
|
|
339
|
-
`container:`. A opção está documentada no próprio arquivo como *"Always
|
|
340
|
-
require a reachable docker daemon, **even if not required by runner**"* —
|
|
341
|
-
ou seja, ela amarra a subida deste runner ao Docker Desktop, que um runner
|
|
342
|
-
`host` não usa. Com o Docker parado, o runner Windows recusa subir sem ter
|
|
343
|
-
motivo próprio para isso.
|
|
344
|
-
|
|
345
|
-
Correção das duas, na ordem:
|
|
346
|
-
|
|
347
|
-
~~~powershell
|
|
348
|
-
Set-Location C:\gitea-runner-win
|
|
349
|
-
if (!(Test-Path .\act_runner.exe)) {
|
|
350
|
-
Copy-Item C:\gitea-runner\act_runner.exe .\act_runner.exe
|
|
351
|
-
}
|
|
352
|
-
.\act_runner.exe --version # precisa casar com o runner Docker: v3.3.1
|
|
353
|
-
~~~
|
|
354
|
-
|
|
355
|
-
No `config.yaml` deste runner, e **somente** neste, deixe:
|
|
356
|
-
|
|
357
|
-
~~~yaml
|
|
358
|
-
container:
|
|
359
|
-
require_docker: false
|
|
360
|
-
~~~
|
|
361
|
-
|
|
362
|
-
O resto do bloco `container:` (socket Docker, `valid_volumes`, `bind_workdir`)
|
|
363
|
-
é inerte aqui, porque um runner `host` não cria container — mas mantê-lo copiado
|
|
364
|
-
é o que faz `require_docker` voltar despercebido na próxima cópia do arquivo.
|
|
365
|
-
|
|
366
|
-
Valide em foreground antes de depender do serviço:
|
|
367
|
-
|
|
368
|
-
~~~powershell
|
|
369
|
-
Set-Location C:\gitea-runner-win
|
|
370
|
-
.\act_runner.exe daemon --config C:\gitea-runner-win\config.yaml
|
|
371
|
-
~~~
|
|
372
|
-
|
|
373
|
-
O sucesso são **duas** linhas, sem `Docker is ready`:
|
|
374
|
-
|
|
375
|
-
~~~text
|
|
376
|
-
level=info msg="Starting runner daemon"
|
|
377
|
-
level=info msg="runner: orquestrador-windows, with version: v3.3.1, with labels: [windows-latest], declare successfully"
|
|
378
|
-
~~~
|
|
379
|
-
|
|
380
|
-
`Docker is ready` aqui significa que `require_docker` voltou a `true`. A label
|
|
381
|
-
sai como `windows-latest` porque o `:host` é o modo de execução do lado do
|
|
382
|
-
runner, não parte do nome da label — `runs-on: windows-latest` no workflow é o
|
|
383
|
-
que casa.
|
|
384
|
-
|
|
385
|
-
O `config.yaml` desse runner aponta
|
|
386
|
-
`ORQUESTRADOR_WINDOWS_PYTHON` para
|
|
387
|
-
`C:\orquestrador\.venv\Scripts\python.exe`. Confira que o ambiente virtual
|
|
388
|
-
existe e tem as dependências do projeto antes de iniciar o serviço.
|
|
389
|
-
|
|
390
|
-
Encerre o foreground com `Ctrl+C` antes de subir o serviço: os dois usam o
|
|
391
|
-
mesmo `.runner.lock` e a mesma identidade registrada.
|
|
392
|
-
|
|
393
|
-
#### Subir o serviço — exige PowerShell elevado
|
|
394
|
-
|
|
395
|
-
`sc.exe start` sem elevação falha com `OpenService FALHA 5: Acesso negado`, e
|
|
396
|
-
esse 5 é do controle do serviço, não do runner. Em PowerShell **como
|
|
397
|
-
administrador**:
|
|
398
|
-
|
|
399
|
-
~~~powershell
|
|
400
|
-
Start-Service GiteaActRunnerWindows
|
|
401
|
-
Get-Service GiteaActRunner, GiteaActRunnerWindows
|
|
402
|
-
~~~
|
|
403
|
-
|
|
404
|
-
Executado pelo operador em 2026-09-13: o serviço ficou `RUNNING` e os **dois**
|
|
405
|
-
runners passaram a reportar `last_online` no mesmo minuto — id 3
|
|
406
|
-
(`orquestrador-local`) e id 4 (`orquestrador-windows`), com 12 segundos de
|
|
407
|
-
diferença. É essa a evidência que fecha a subida dos dois runners; serviço
|
|
408
|
-
`RUNNING` sozinho não prova conexão com o Gitea.
|
|
409
|
-
|
|
410
|
-
Só crie o serviço com NSSM se `sc.exe query` disser que ele não existe. Ele
|
|
411
|
-
precisa de diretório de trabalho próprio e não pode reutilizar o registro do
|
|
412
|
-
runner Docker:
|
|
413
|
-
|
|
414
|
-
~~~powershell
|
|
415
|
-
$nssm = (Get-Command nssm.exe).Source
|
|
416
|
-
& $nssm install GiteaActRunnerWindows C:\gitea-runner-win\act_runner.exe
|
|
417
|
-
& $nssm set GiteaActRunnerWindows AppDirectory C:\gitea-runner-win
|
|
418
|
-
& $nssm set GiteaActRunnerWindows AppParameters 'daemon --config C:\gitea-runner-win\config.yaml'
|
|
419
|
-
& $nssm set GiteaActRunnerWindows Start SERVICE_AUTO_START
|
|
420
|
-
Start-Service GiteaActRunnerWindows
|
|
421
|
-
~~~
|
|
422
|
-
|
|
423
|
-
O modo `host` expõe o ambiente Windows, ferramentas e credenciais da máquina
|
|
424
|
-
ao job. Use-o apenas para workflows confiáveis.
|
|
425
|
-
|
|
426
|
-
### Secrets e workflows
|
|
427
|
-
|
|
428
|
-
Secrets do GitHub não são copiados automaticamente para o Gitea. Cadastre no
|
|
429
|
-
repositório Gitea, em **Settings → Actions → Secrets**, os valores exigidos
|
|
430
|
-
pelos workflows, como `OPENROUTER_API_KEY`, `CONTEXT7_API_KEY`,
|
|
431
|
-
`TWENTY_FIRST_API_KEY` e `GEMINI_API_KEY` quando aplicável. Tokens de clone e
|
|
432
|
-
tokens de registro de runner devem ser tratados separadamente e nunca entrar
|
|
433
|
-
no repositório.
|
|
434
|
-
|
|
435
|
-
Os arquivos em `.gitea/workflows/` são executados pelo Gitea. Os arquivos em
|
|
436
|
-
`.github/workflows/` continuam sendo executados pelo GitHub Actions; o fato de
|
|
437
|
-
o conteúdo ser espelhado não faz o Gitea executar workflows do GitHub. Dentro
|
|
438
|
-
de um job Docker, use `http://host.docker.internal:3000` para acessar o Gitea;
|
|
439
|
-
`localhost` aponta para o próprio container.
|
|
440
|
-
|
|
441
|
-
### Diagnóstico e manutenção
|
|
442
|
-
|
|
443
|
-
~~~powershell
|
|
444
|
-
Set-Location C:\gitea
|
|
445
|
-
docker compose ps
|
|
446
|
-
docker compose logs --tail 100 server
|
|
447
|
-
Get-Service GiteaActRunner
|
|
448
|
-
Get-Content C:\gitea-runner\service.err.log -Tail 80
|
|
449
|
-
Get-Service GiteaActRunnerWindows -ErrorAction SilentlyContinue
|
|
450
|
-
Get-Content C:\gitea-runner-win\service.err.log -Tail 80 -ErrorAction SilentlyContinue
|
|
451
|
-
~~~
|
|
452
|
-
|
|
453
|
-
Serviço `Running` prova que o processo existe, não que o runner está falando
|
|
454
|
-
com o Gitea. Quem responde isso é o `last_online` do servidor, e ele é
|
|
455
|
-
consultável sem abrir a interface e sem autenticar:
|
|
456
|
-
|
|
457
|
-
~~~powershell
|
|
458
|
-
docker exec gitea sqlite3 /data/gitea/gitea.db "select id, name, version, agent_labels, datetime(last_online,'unixepoch','localtime') from action_runner where deleted is null;"
|
|
459
|
-
~~~
|
|
460
|
-
|
|
461
|
-
**O `where deleted is null` não é opcional.** O Gitea apaga runner por *soft
|
|
462
|
-
delete*: a linha permanece em `action_runner` com a coluna `deleted`
|
|
463
|
-
preenchida, e a interface e a API deixam de listá-la. Uma consulta sem esse
|
|
464
|
-
filtro mostra registros já removidos como se estivessem ativos — foi o que
|
|
465
|
-
aconteceu em 2026-09-13, quando os ids 1 e 2 foram lidos como "órfãos
|
|
466
|
-
aparecendo offline na interface" e na verdade já tinham sido excluídos em
|
|
467
|
-
2026-08-30, minutos depois de criados e antes do id 3 existir.
|
|
468
|
-
|
|
469
|
-
Um `last_online` recente é o runner online agora. Os registros vivos nesta
|
|
470
|
-
máquina são o id 3 (`orquestrador-local`, labels `ubuntu-*`) e o id 4
|
|
471
|
-
(`orquestrador-windows`, label `windows-latest`). Para conferir o que a
|
|
472
|
-
interface de fato mostra, sem depender da leitura do banco:
|
|
473
|
-
|
|
474
|
-
~~~powershell
|
|
475
|
-
docker exec gitea sqlite3 /data/gitea/gitea.db "select id, datetime(deleted,'unixepoch','localtime') from action_runner;"
|
|
476
|
-
~~~
|
|
477
|
-
|
|
478
|
-
Nunca apague linha de `action_runner` por SQL: remover runner é operação de
|
|
479
|
-
interface, e o soft delete existe para preservar a trilha dos jobs que aquele
|
|
480
|
-
runner atendeu.
|
|
481
|
-
|
|
482
|
-
Se um runner for removido pela interface do Gitea, pare o daemon e remova
|
|
483
|
-
somente `.runner` e `.runner.lock` no diretório daquele runner; preserve
|
|
484
|
-
`config.yaml`, registre um novo token e valide novamente. Nunca remova
|
|
485
|
-
`C:\gitea\gitea` para corrigir um problema de runner.
|
|
486
|
-
|
|
487
|
-
No diagnóstico desta máquina, em 2026-09-11, o endpoint do Gitea recusou
|
|
488
|
-
conexão porque o container estava parado e `C:\gitea-runner\service.err.log`
|
|
489
|
-
registrou `Docker Engine socket not found`. A sequência de recuperação é:
|
|
490
|
-
|
|
491
|
-
~~~powershell
|
|
492
|
-
Set-Location C:\gitea
|
|
493
|
-
docker compose up -d
|
|
494
|
-
docker info
|
|
495
|
-
Restart-Service GiteaActRunner
|
|
496
|
-
Get-Content C:\gitea-runner\service.err.log -Tail 80
|
|
497
|
-
~~~
|
|
498
|
-
|
|
499
|
-
Em 2026-09-13 o Gitea (`1.27.3`) e o runner Docker estavam saudáveis, e só o
|
|
500
|
-
runner Windows estava parado: binário ausente e `require_docker: true` herdado.
|
|
501
|
-
A lição de diagnóstico é que os dois runners falham por causas **disjuntas** —
|
|
502
|
-
o Docker depende do Docker Desktop e do container do Gitea; o Windows/host não
|
|
503
|
-
depende de nenhum dos dois. Tratar "os runners não sobem" como um problema só
|
|
504
|
-
faz procurar causa comum onde não há.
|
|
505
|
-
|
|
506
|
-
## 2.2. Variáveis de ambiente
|
|
507
|
-
|
|
508
|
-
O arquivo [`.env.example`](.env.example) é o catálogo versionado das variáveis
|
|
509
|
-
suportadas. O `.env` local é ignorado pelo Git e não deve ser publicado.
|
|
510
|
-
Para as credenciais cadastradas no ambiente de usuário do Windows, use referências
|
|
511
|
-
sem copiar os valores:
|
|
512
|
-
|
|
513
|
-
```dotenv
|
|
514
|
-
OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
|
|
515
|
-
CONTEXT7_API_KEY=${CONTEXT7_API_KEY}
|
|
516
|
-
TWENTY_FIRST_API_KEY=${TWENTY_FIRST_API_KEY}
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
Abra um novo PowerShell depois de alterar variáveis no Windows. Confira somente
|
|
520
|
-
a presença das credenciais:
|
|
521
|
-
|
|
522
|
-
```powershell
|
|
523
|
-
python -c "import os; from dotenv import load_dotenv; load_dotenv('.env', override=False); names=['OPENROUTER_API_KEY','CONTEXT7_API_KEY','TWENTY_FIRST_API_KEY']; print({n:bool(os.environ.get(n)) for n in names})"
|
|
524
|
-
git check-ignore -v .env
|
|
525
|
-
python scripts/validate_agent_policy.py validate
|
|
526
|
-
python scripts/plan_status.py validate
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
As variáveis estão organizadas no `.env.example` por provider LLM, OpenRouter,
|
|
530
|
-
MCP, Gitea/Git, sandbox, retry, kernel, orçamento, observabilidade, skills,
|
|
531
|
-
HITL, identidade e mídia. O modelo padrão operacional é
|
|
532
|
-
`deepseek/deepseek-v4-flash-0731`; o Backend usa `reasoning_effort=max` na
|
|
533
|
-
política central `config/agent-policy.json`. Não habilite overrides legados.
|
|
534
|
-
|
|
535
|
-
## 2.3. Supermemory local (memória entre sessões)
|
|
536
|
-
|
|
537
|
-
Desde 2026-09-23, por decisão do operador, a memória entre sessões mora no
|
|
538
|
-
`supermemory-server` rodando no **WSL Ubuntu** da máquina do operador. O plano da
|
|
539
|
-
nuvem chegou ao limite e passou a recusar gravações, e o conector da nuvem foi
|
|
540
|
-
**desativado** no mesmo dia (claude.ai → Settings → Connectors → Supermemory). As
|
|
541
|
-
regras de uso estão no `AGENTS.md`, subseção "Supermemory: o servidor local do
|
|
542
|
-
operador".
|
|
543
|
-
|
|
544
|
-
Tudo abaixo roda **dentro do Ubuntu** (`wsl -d Ubuntu`), a partir de `~`, nunca
|
|
545
|
-
de `/mnt/c/...`: a pasta de dados segue o diretório atual quando não é fixada, e
|
|
546
|
-
`/mnt/c` é o disco do Windows, mais lento.
|
|
547
|
-
|
|
548
|
-
### Instalar (uma vez)
|
|
549
|
-
|
|
550
|
-
```bash
|
|
551
|
-
sudo apt update && sudo apt install -y curl
|
|
552
|
-
cd ~ && curl -fsSL https://supermemory.ai/install | bash
|
|
553
|
-
echo 'export PATH="$HOME/.supermemory/bin:$PATH"' >> ~/.bashrc
|
|
554
|
-
```
|
|
555
|
-
|
|
556
|
-
### Configuração fixa (uma vez)
|
|
557
|
-
|
|
558
|
-
Embedding multilíngue e local, com a pasta de dados fixada. Nada disso é
|
|
559
|
-
segredo. O `embedding-plan.json` que o assistente grava **não** é o que o
|
|
560
|
-
servidor carrega; sem estas variáveis ele sobe com o modelo inglês de 768
|
|
561
|
-
dimensões, que não combina com as tabelas de 1024 (medido em 2026-09-23).
|
|
562
|
-
|
|
563
|
-
```bash
|
|
564
|
-
cat >> ~/.bashrc <<'EOF'
|
|
565
|
-
export SUPERMEMORY_EMBEDDING_PROVIDER=local
|
|
566
|
-
export SUPERMEMORY_EMBEDDING_MODEL=Xenova/bge-m3
|
|
567
|
-
export SUPERMEMORY_EMBEDDING_DIMENSIONS=1024
|
|
568
|
-
export SUPERMEMORY_DATA_DIR="$HOME/.supermemory"
|
|
569
|
-
EOF
|
|
570
|
-
source ~/.bashrc
|
|
571
|
-
```
|
|
572
|
-
|
|
573
|
-
Chave do LLM que extrai as memórias (GLM, pela API compatível com OpenAI da
|
|
574
|
-
Z.ai). A chave vai para um arquivo **fora** de `~/.supermemory`, porque o
|
|
575
|
-
servidor lê o `~/.supermemory/env`, cifra o conteúdo em `env.enc` e apaga o
|
|
576
|
-
original; e a extração roda num processo que só enxerga variáveis exportadas.
|
|
577
|
-
|
|
578
|
-
```bash
|
|
579
|
-
mkdir -p ~/.config/glm && chmod 700 ~/.config/glm
|
|
580
|
-
read -rsp "Chave GLM: " K; echo; printf '%s' "$K" > ~/.config/glm/key; unset K
|
|
581
|
-
chmod 600 ~/.config/glm/key
|
|
582
|
-
|
|
583
|
-
cat >> ~/.bashrc <<'EOF'
|
|
584
|
-
sm() { ( export OPENAI_API_KEY="$(cat "$HOME/.config/glm/key")" \
|
|
585
|
-
OPENAI_BASE_URL=https://api.z.ai/api/coding/paas/v4 \
|
|
586
|
-
OPENAI_MODEL=glm-4.6 OPENAI_FAST_MODEL=glm-4.5-air
|
|
587
|
-
exec supermemory-server "$@" ); }
|
|
588
|
-
EOF
|
|
589
|
-
source ~/.bashrc
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
`OPENAI_BASE_URL` depende do plano da Z.ai: com a assinatura de código use a URL
|
|
593
|
-
`coding` acima; com saldo pré-pago, `https://api.z.ai/api/paas/v4`. A URL errada
|
|
594
|
-
responde `Insufficient balance or no resource package`.
|
|
595
|
-
|
|
596
|
-
### Subir e conferir
|
|
597
|
-
|
|
598
|
-
```bash
|
|
599
|
-
cd ~ && sm # sobe em http://localhost:6767 e fica em primeiro plano
|
|
600
|
-
supermemory-server doctor # noutro terminal: provedor, embedding, pasta de dados
|
|
601
|
-
```
|
|
602
|
-
|
|
603
|
-
O quadro `supermemory ready` precisa mostrar `database /home/<você>/.supermemory` e
|
|
604
|
-
`embeddings local · Xenova/bge-m3 · 1024d`. **Não compartilhe prints desse quadro:
|
|
605
|
-
a linha `api key` traz a chave inteira.**
|
|
606
|
-
|
|
607
|
-
### Testar
|
|
608
|
-
|
|
609
|
-
```bash
|
|
610
|
-
# grava um documento; no terminal do servidor a linha final deve terminar em "N memories"
|
|
611
|
-
curl -s http://localhost:6767/v3/documents -H "Content-Type: application/json" \
|
|
612
|
-
-d '{"content":"Teste do Supermemory local.","containerTag":"orquestrador"}'; echo
|
|
613
|
-
|
|
614
|
-
# busca: containerTags é LISTA. Sem ela, ou com containerTag no singular, a
|
|
615
|
-
# resposta é 200 com resultado vazio -- parece "nada gravado" e não é.
|
|
616
|
-
curl -s http://localhost:6767/v4/search -H "Content-Type: application/json" \
|
|
617
|
-
-d '{"q":"supermemory local","containerTags":["orquestrador"],"searchMode":"memories","threshold":0}'; echo
|
|
618
|
-
```
|
|
619
|
-
|
|
620
|
-
Mande sempre `threshold`. Sem ele o servidor aplica um limiar próprio e descarta
|
|
621
|
-
memória relevante: medido em 2026-09-23, "Hermes cron workdir" devolveu 1 memória
|
|
622
|
-
sem `threshold` e as do mesmo assunto com `threshold: 0`. Com 0, a busca devolve
|
|
623
|
-
as mais próximas até o `limit`, cada uma com a `similarity` ao lado. O cliente do
|
|
624
|
-
orquestrador manda o valor de `config/durable-stores.json`,
|
|
625
|
-
`memory_store.search_threshold`.
|
|
626
|
-
|
|
627
|
-
Teste de ponta a ponta de **todas** as chamadas que o orquestrador faz (gravar,
|
|
628
|
-
buscar, editar, grafo, ler, esquecer, apagar), num espaço descartável
|
|
629
|
-
`orquestrador-smoke`, que ele limpa no fim. Cada linha diz `OK` ou `FAIL` com o
|
|
630
|
-
motivo que o servidor deu:
|
|
631
|
-
|
|
632
|
-
```bash
|
|
633
|
-
PYTHONPATH=$PWD .venv/bin/python scripts/supermemory_local_smoke.py
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
No **Windows**, que é onde a `.venv` do projeto mora, rode pelo PowerShell a
|
|
637
|
-
partir do checkout — o Windows alcança o servidor do WSL por `127.0.0.1:6767`.
|
|
638
|
-
Os scripts abaixo (migração e sync) seguem a mesma forma:
|
|
639
|
-
|
|
640
|
-
```powershell
|
|
641
|
-
cd C:\orquestrador # ou a worktree da branch que tem os scripts
|
|
642
|
-
$env:PYTHONPATH = (Get-Location).Path
|
|
643
|
-
$env:PYTHONUTF8 = "1" # o console cp1252 quebra no ✅ das memórias
|
|
644
|
-
$py = "C:\orquestrador\.venv\Scripts\python.exe"
|
|
645
|
-
& $py scripts\supermemory_local_smoke.py
|
|
646
|
-
```
|
|
647
|
-
|
|
648
|
-
A busca do smoke tenta de novo por até 30 s antes de reprovar: o servidor indexa
|
|
649
|
-
a memória nova na própria fila, e uma espera fixa confundia "ainda não indexou"
|
|
650
|
-
com "a busca não funciona".
|
|
651
|
-
|
|
652
|
-
### Listar as memórias
|
|
653
|
-
|
|
654
|
-
```bash
|
|
655
|
-
# documentos do espaço, paginados
|
|
656
|
-
curl -s http://localhost:6767/v3/documents/list -H "Content-Type: application/json" \
|
|
657
|
-
-d '{"containerTags":["orquestrador"],"page":1,"limit":50}'; echo
|
|
658
|
-
|
|
659
|
-
# um documento com as memórias extraídas dele
|
|
660
|
-
curl -s http://localhost:6767/v3/documents/<documentId>; echo
|
|
661
|
-
```
|
|
662
|
-
|
|
663
|
-
Pelo orquestrador, o grafo inteiro (todos os documentos com as memórias de cada
|
|
664
|
-
um) sai de `GET /memory/graph` da API HTTP (seção 7), autenticado como as outras
|
|
665
|
-
rotas.
|
|
666
|
-
|
|
667
|
-
### Migrar as memórias da nuvem (uma vez)
|
|
668
|
-
|
|
669
|
-
Com o servidor de pé, a partir do checkout do orquestrador:
|
|
670
|
-
|
|
671
|
-
```bash
|
|
672
|
-
PYTHONPATH=$PWD .venv/bin/python scripts/supermemory_migrate_to_local.py --dry-run
|
|
673
|
-
PYTHONPATH=$PWD .venv/bin/python scripts/supermemory_migrate_to_local.py
|
|
674
|
-
```
|
|
675
|
-
|
|
676
|
-
São as 33 memórias de `docs/supermemory/cloud-export-2026-09-23.json`, já
|
|
677
|
-
normalizadas para o padrão de gravação, escritas com o texto exato, sem reprocessar
|
|
678
|
-
no LLM. O script confere o export inteiro contra o padrão **antes** da primeira
|
|
679
|
-
gravação — `--dry-run` lista em `invalid` o que seria recusado — e guarda em
|
|
680
|
-
`data/supermemory-migration.json` o que já foi aceito: rodar de novo não duplica.
|
|
681
|
-
|
|
682
|
-
### Padrão de gravação (aplicado pelo código)
|
|
683
|
-
|
|
684
|
-
Toda gravação — MCP, tela, migração, inbox — passa por `src/memory/standard.py`,
|
|
685
|
-
que recusa e nomeia cada violação. O formato que passa:
|
|
686
|
-
|
|
687
|
-
```text
|
|
688
|
-
TEC-002 was delivered on 2026-09-22 in commit 93bbf57.
|
|
689
|
-
STK-004 was marked ✅, meaning developed and validated within the scope of the cited evidence, not approved by the operator, on 2026-09-22 in commit d2ab40d.
|
|
690
|
-
```
|
|
691
|
-
|
|
692
|
-
Uma frase, em inglês, com data ISO e âncora (SHA, id de item ou `session <id>`),
|
|
693
|
-
ícone com o sentido escrito, sem estado volátil ("still pending", "in progress")
|
|
694
|
-
e sem segredo. Os valores estão em `config/durable-stores.json`,
|
|
695
|
-
`memory_store.standard`.
|
|
696
|
-
|
|
697
|
-
### Sincronizar com as sessões da nuvem
|
|
698
|
-
|
|
699
|
-
Sessões na nuvem não alcançam este servidor. Elas leem
|
|
700
|
-
`docs/supermemory/snapshot.json` e gravam em `docs/supermemory/inbox/`, que chegam
|
|
701
|
-
aqui pelo git. Depois de trazer as branches mescladas para o checkout:
|
|
702
|
-
|
|
703
|
-
```bash
|
|
704
|
-
PYTHONPATH=$PWD .venv/bin/python scripts/supermemory_sync.py
|
|
705
|
-
git add docs/supermemory/snapshot.json
|
|
706
|
-
git commit -m "memory: snapshot do servidor local" -- docs/supermemory/snapshot.json
|
|
707
|
-
```
|
|
708
|
-
|
|
709
|
-
O script aplica cada operação do inbox uma vez (o registro fica em
|
|
710
|
-
`data/supermemory-sync.json`) e reescreve o snapshot a partir do servidor. Rode-o
|
|
711
|
-
depois da migração, para o snapshot passar a vir do servidor, e sempre que um
|
|
712
|
-
inbox novo chegar. O Hermes pode rodá-lo na mesma agenda do laço do plano.
|
|
713
|
-
|
|
714
|
-
As memórias seguidas de uma mesma sessão vão numa chamada só, e o servidor faz um
|
|
715
|
-
documento por chamada: no grafo, cada sessão aparece como um documento com os
|
|
716
|
-
fatos em volta. Até 2026-09-23 cada memória ia sozinha, e o grafo mostrava um par
|
|
717
|
-
documento-memória para cada uma.
|
|
718
|
-
|
|
719
|
-
### Reagrupar as memórias gravadas uma por documento (uma vez)
|
|
720
|
-
|
|
721
|
-
As 42 memórias gravadas antes dessa mudança continuam uma por documento. O
|
|
722
|
-
reagrupamento junta cada lote de gravação da nuvem e cada sessão do inbox num
|
|
723
|
-
documento só. Ele só apaga um documento antigo **depois** de conferir que cada
|
|
724
|
-
memória dele está no documento novo, e no fim confere que o conjunto de memórias
|
|
725
|
-
é o mesmo de antes, sem perda e sem duplicata. Memória que o repositório não
|
|
726
|
-
conhece (gravada pela tela, por exemplo) fica onde está.
|
|
727
|
-
|
|
728
|
-
```powershell
|
|
729
|
-
git fetch origin claude/nifty-knuth-27gns3
|
|
730
|
-
git checkout origin/claude/nifty-knuth-27gns3
|
|
731
|
-
& $py scripts\supermemory_sync.py # aplica o que estiver no inbox
|
|
732
|
-
& $py scripts\supermemory_regroup.py # só o plano, não toca em nada
|
|
733
|
-
& $py scripts\supermemory_regroup.py --apply # "verified": true no fim
|
|
734
|
-
& $py scripts\supermemory_sync.py # snapshot a partir do servidor
|
|
735
|
-
git add docs/supermemory/snapshot.json
|
|
736
|
-
git commit -m "memory: snapshot reagrupado" -- docs/supermemory/snapshot.json
|
|
737
|
-
```
|
|
738
|
-
|
|
739
|
-
`--apply` sai `0` só com `"verified": true`. Se sair `1`, nada foi apagado antes
|
|
740
|
-
do problema: o relatório nomeia o grupo que o servidor não confirmou. Rodar de
|
|
741
|
-
novo é seguro.
|
|
742
|
-
|
|
743
|
-
### Sessões e Hermes: o MCP `supermemory-local`
|
|
744
|
-
|
|
745
|
-
O `.mcp.json` declara `supermemory-local`, um MCP `stdio` que chama o servidor
|
|
746
|
-
local. Ferramentas: `search_memory`, `add_memory` (`action` `save`/`forget`),
|
|
747
|
-
`list_memories`, `get_document`, `update_memory`, `delete_document`. O
|
|
748
|
-
interpretador vem de `ORQUESTRADOR_PYTHON`:
|
|
749
|
-
|
|
750
|
-
```powershell
|
|
751
|
-
# Windows (checkout em C:\orquestrador)
|
|
752
|
-
[System.Environment]::SetEnvironmentVariable("ORQUESTRADOR_PYTHON", "C:\orquestrador\.venv\Scripts\python.exe", "User")
|
|
753
|
-
```
|
|
754
|
-
|
|
755
|
-
```bash
|
|
756
|
-
# Linux/WSL
|
|
757
|
-
echo "export ORQUESTRADOR_PYTHON=$PWD/.venv/bin/python" >> ~/.bashrc
|
|
758
|
-
```
|
|
759
|
-
|
|
760
|
-
Depois, em uma sessão nova, `/mcp` deve listar `supermemory-local` conectado.
|
|
761
|
-
Se o interpretador escolhido não tiver o pacote `mcp`, o servidor se entrega ao
|
|
762
|
-
`python` da `.venv` do projeto. Sessões na nuvem (claude.ai/code) **não** alcançam
|
|
763
|
-
o `localhost` do operador: o mesmo MCP responde a elas pelo snapshot e grava no
|
|
764
|
-
inbox, com o erro do servidor ao lado para ninguém confundir espelho com servidor.
|
|
765
|
-
Para o MCP subir num container da nuvem, o ambiente precisa da `.venv` com
|
|
766
|
-
`requirements.txt` instalado — o script de setup do ambiente é o lugar.
|
|
767
|
-
|
|
768
|
-
Hermes, em `~/.hermes/config.yaml`. O nome `supermemory` mantém as ferramentas
|
|
769
|
-
`mcp_supermemory_search_memory` e `mcp_supermemory_add_memory` que a skill
|
|
770
|
-
`orquestrador-plan-loop` já chama:
|
|
771
|
-
|
|
772
|
-
```yaml
|
|
773
|
-
mcp_servers:
|
|
774
|
-
supermemory:
|
|
775
|
-
command: /caminho/do/orquestrador/.venv/bin/python
|
|
776
|
-
args: ["/caminho/do/orquestrador/mcp_servers/supermemory_local_server.py"]
|
|
777
|
-
```
|
|
778
|
-
|
|
779
|
-
### Espaço de desenvolvimento e despacho contínuo pelo Hermes
|
|
780
|
-
|
|
781
|
-
Desde 2026-09-24 a memória tem dois espaços. O geral (`orquestrador`) guarda
|
|
782
|
-
decisões e achados; o de desenvolvimento (`orquestrador-dev`) guarda só os
|
|
783
|
-
registros `[dispatch]`, `[result]`, `[blocked]` e `[compact]` do despacho. O mesmo
|
|
784
|
-
servidor MCP atende os dois: `supermemory-dev` é ele com
|
|
785
|
-
`SUPERMEMORY_LOCAL_SPACE=dev`, já declarado no `.mcp.json`. O espaço dev não tem
|
|
786
|
-
espelho versionado; sem o servidor, ele responde com o erro e nada mais.
|
|
787
|
-
|
|
788
|
-
O Hermes despacha cada task do plano para a sessão Claude Code persistente do
|
|
789
|
-
papel dela (`backend`, `frontend`, `docs`, `dba`), um turno por task com
|
|
790
|
-
`claude -p --resume`, até o plano acabar. Política em `config/dev-dispatch.json`;
|
|
791
|
-
instalação, pré-requisitos (login do Claude Code, uma worktree e uma sessão por
|
|
792
|
-
papel, o campo `role` nos itens) e operação em
|
|
793
|
-
[`.hermes/skills/orquestrador-dev-dispatcher/SKILL.md`](.hermes/skills/orquestrador-dev-dispatcher/SKILL.md).
|
|
794
|
-
Para ver o que ele faria sem lançar nada:
|
|
795
|
-
|
|
796
|
-
```powershell
|
|
797
|
-
$env:HERMES_HOME = "$env:LOCALAPPDATA\hermes"
|
|
798
|
-
C:\orquestrador\.venv\Scripts\python.exe .hermes\skills\orquestrador-dev-dispatcher\scripts\dispatch_tick.py --repo C:\orquestrador --dry-run
|
|
799
|
-
```
|
|
800
|
-
|
|
801
|
-
### Segurança da porta 6767
|
|
802
|
-
|
|
803
|
-
O servidor escuta em `0.0.0.0:6767` (`ss -ltn | grep 6767`), e não há opção
|
|
804
|
-
documentada para prendê-lo ao `localhost`. No WSL2 em modo NAT, o padrão, só o
|
|
805
|
-
próprio Windows o alcança. Se o `.wslconfig` usa `networkingMode=mirrored`, ou se
|
|
806
|
-
houver `portproxy`, a rede local também alcança. Bloqueie a entrada vinda da rede
|
|
807
|
-
no firewall do Windows (PowerShell como administrador). O tráfego de loopback
|
|
808
|
-
não passa por essa regra:
|
|
809
|
-
|
|
810
|
-
```powershell
|
|
811
|
-
New-NetFirewallRule -DisplayName "Supermemory local - bloqueia rede" -Direction Inbound `
|
|
812
|
-
-Protocol TCP -LocalPort 6767 -RemoteAddress LocalSubnet,Internet -Action Block
|
|
813
|
-
```
|
|
814
|
-
|
|
815
|
-
## 3. Modelos Groq
|
|
816
|
-
|
|
817
|
-
Os perfis podem ser alterados sem modificar código:
|
|
818
|
-
|
|
819
|
-
```dotenv
|
|
820
|
-
ORQUESTRADOR_MODEL_STRUCTURED=openai/gpt-oss-20b
|
|
821
|
-
ORQUESTRADOR_MODEL_CODING=openai/gpt-oss-120b
|
|
822
|
-
ORQUESTRADOR_MODEL_REASONING=openai/gpt-oss-120b
|
|
823
|
-
ORQUESTRADOR_MODEL_RESEARCH=groq/compound
|
|
824
|
-
ORQUESTRADOR_MODEL_RESEARCH_FAST=groq/compound-mini
|
|
825
|
-
ORQUESTRADOR_MODEL_INPUT_GUARD=meta-llama/llama-prompt-guard-2-86m
|
|
826
|
-
ORQUESTRADOR_MODEL_OUTPUT_GUARD=openai/gpt-oss-safeguard-20b
|
|
827
|
-
```
|
|
828
|
-
|
|
829
|
-
Mapa recomendado, conforme a documentação oficial da Groq:
|
|
830
|
-
|
|
831
|
-
| Perfil/modelo | Janela | Máx. saída | Uso |
|
|
832
|
-
|---|---:|---:|---|
|
|
833
|
-
| `openai/gpt-oss-20b` | 131.072 | 65.536 | router, classificações e JSON estruturado rápido |
|
|
834
|
-
| `openai/gpt-oss-120b` | 131.072 | 65.536 | código, arquitetura, review, compliance e DBA |
|
|
835
|
-
| `groq/compound` | 131.072 | 8.192 | pesquisa/agentes com ferramentas Groq |
|
|
836
|
-
| `groq/compound-mini` | 131.072 | 8.192 | pesquisa rápida e sumarização |
|
|
837
|
-
| `meta-llama/llama-prompt-guard-2-86m` | 512 | 512 | classificação de prompt de entrada |
|
|
838
|
-
| `openai/gpt-oss-safeguard-20b` | 131.072 | 65.536 | inspeção de saída/safety |
|
|
839
|
-
| `qwen/qwen3.6-27b` | 131.072 | 16.384 | alternativa experimental; modelo preview e mais caro |
|
|
840
|
-
|
|
841
|
-
O perfil estruturado usa `json_schema` nos GPT-OSS; o perfil de
|
|
842
|
-
código/raciocínio usa o GPT-OSS 120B. Compound fica reservado para pesquisa
|
|
843
|
-
com ferramentas. Prompt Guard e Safeguard permanecem nos portões de segurança
|
|
844
|
-
e foram validados pelo smoke real do workflow Groq.
|
|
845
|
-
|
|
846
|
-
Os números da tabela são capacidades máximas dos modelos, não a quota da
|
|
847
|
-
conta. No tier on-demand com 8.000 TPM, configure limites conservadores para
|
|
848
|
-
que entrada mais saída solicitada caibam na quota:
|
|
849
|
-
|
|
850
|
-
```dotenv
|
|
851
|
-
ORQUESTRADOR_LLM_MAX_INPUT_CHARS=12000
|
|
852
|
-
ORQUESTRADOR_GROQ_MAX_COMPLETION_TOKENS=2048
|
|
853
|
-
```
|
|
854
|
-
|
|
855
|
-
Ao migrar para um tier superior, esses dois valores podem ser aumentados sem
|
|
856
|
-
alterar o código.
|
|
857
|
-
|
|
858
|
-
Para OpenRouter, os limites não ficam presos aos valores conservadores da
|
|
859
|
-
Groq. O catálogo operacional por modelo está centralizado em
|
|
860
|
-
[`src/llm/llm_model_config.py`](src/llm/llm_model_config.py). A troca global do modelo
|
|
861
|
-
é feita em um único ponto:
|
|
862
|
-
|
|
863
|
-
| Modelo agentic | Contexto | Máx. saída | Input / 1M | Output / 1M |
|
|
864
|
-
|---|---:|---:|---:|---:|
|
|
865
|
-
| `deepseek/deepseek-v4-flash-0731` | 1.310.720 | 943.718 | US$ 0,04 | US$ 0,08 |
|
|
866
|
-
| `minimax/minimax-m3` (fallback produção) | 1.048.576 | 512.000 | US$ 0,30 | US$ 1,20 |
|
|
867
|
-
| `moonshotai/kimi-k2.7-code` (fallback produção) | 262.144 | 235.929 | US$ 0,67 | US$ 3,40 |
|
|
868
|
-
|
|
869
|
-
Os valores são snapshot do catálogo e não substituem o preço efetivo da
|
|
870
|
-
resposta. O adapter registra usage/custo retornados pelo OpenRouter.
|
|
871
|
-
|
|
872
|
-
```dotenv
|
|
873
|
-
ORQUESTRADOR_LLM_PROVIDER=openrouter
|
|
874
|
-
ORQUESTRADOR_AGENT_MODEL=deepseek/deepseek-v4-flash-0731
|
|
875
|
-
```
|
|
876
|
-
|
|
877
|
-
O `DeepSeek V4 Flash 0731` está registrado com contexto de 1.310.720 tokens e
|
|
878
|
-
até 943.718 tokens de saída. O limite de entrada operacional é de 1.000.000
|
|
879
|
-
de caracteres para evitar enviar acidentalmente o repositório inteiro em uma
|
|
880
|
-
única chamada. Esses valores podem ser sobrescritos temporariamente com
|
|
881
|
-
`ORQUESTRADOR_MODEL_CONFIG_JSON`, sem editar agentes. Um teto operacional por
|
|
882
|
-
ambiente (`ORQUESTRADOR_OPENROUTER_MAX_COMPLETION_TOKENS` ou
|
|
883
|
-
`ORQUESTRADOR_LLM_MAX_INPUT_CHARS`) continua disponível e tem precedência.
|
|
884
|
-
|
|
885
|
-
Referências oficiais: [Quickstart Groq](https://console.groq.com/docs/quickstart),
|
|
886
|
-
[modelos](https://console.groq.com/docs/models), [API compatível com OpenAI](https://console.groq.com/docs/openai),
|
|
887
|
-
[Structured Outputs](https://console.groq.com/docs/structured-outputs),
|
|
888
|
-
[LangChain + Groq](https://console.groq.com/docs/langchain),
|
|
889
|
-
[Prompt Caching](https://console.groq.com/docs/prompt-caching) e
|
|
890
|
-
[limites](https://console.groq.com/docs/rate-limits).
|
|
891
|
-
Páginas dos modelos: [Compound](https://console.groq.com/docs/compound),
|
|
892
|
-
[Compound mini](https://console.groq.com/docs/compound/systems/compound-mini),
|
|
893
|
-
[GPT-OSS 120B](https://console.groq.com/docs/model/openai/gpt-oss-120b),
|
|
894
|
-
[GPT-OSS 20B](https://console.groq.com/docs/model/openai/gpt-oss-20b),
|
|
895
|
-
[Qwen 3.6 27B](https://console.groq.com/docs/model/qwen/qwen3.6-27b),
|
|
896
|
-
[Prompt Guard 86M](https://console.groq.com/docs/model/meta-llama/llama-prompt-guard-2-86m) e
|
|
897
|
-
[Safeguard 20B](https://console.groq.com/docs/model/openai/gpt-oss-safeguard-20b).
|
|
898
|
-
|
|
899
|
-
## 4. Roteamento multi-provedor
|
|
900
|
-
|
|
901
|
-
Os agentes usam uma fábrica única (`src/llm/llm_provider.py`) e podem ser
|
|
902
|
-
direcionados individualmente, por perfil ou globalmente. O comportamento
|
|
903
|
-
legado continua sendo Groq por padrão. As credenciais são sempre lidas de
|
|
904
|
-
variáveis de ambiente:
|
|
905
|
-
|
|
906
|
-
```dotenv
|
|
907
|
-
# Provedor único para todos os agentes
|
|
908
|
-
ORQUESTRADOR_LLM_PROVIDER=groq
|
|
909
|
-
GROQ_API_KEY=...
|
|
910
|
-
|
|
911
|
-
# Alternativas suportadas pelo adapter
|
|
912
|
-
ANTHROPIC_API_KEY=...
|
|
913
|
-
OPENAI_API_KEY=...
|
|
914
|
-
GEMINI_API_KEY=...
|
|
915
|
-
```
|
|
916
|
-
|
|
917
|
-
Para escolher provedor e modelo por agente, use JSON no secret/variável
|
|
918
|
-
`ORQUESTRADOR_LLM_ROUTING_JSON` (não coloque chaves dentro do JSON):
|
|
12
|
+
Instale a versão publicada do npm diretamente no harness. Para fixar uma
|
|
13
|
+
versão em produção, use o número explícito no argumento do pacote:
|
|
919
14
|
|
|
920
15
|
```json
|
|
921
16
|
{
|
|
922
|
-
"
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
17
|
+
"mcpServers": {
|
|
18
|
+
"jev-browser": {
|
|
19
|
+
"command": "npx",
|
|
20
|
+
"args": ["--yes", "@diegosouzacdv/jev-browser-mcp@0.4.0"],
|
|
21
|
+
"env": {
|
|
22
|
+
"OPENROUTER_API_KEY": "${OPENROUTER_API_KEY}",
|
|
23
|
+
"JEV_BROWSER_MODE": "computer"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
931
26
|
}
|
|
932
27
|
}
|
|
933
28
|
```
|
|
934
29
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
30
|
+
O formato de interpolação de variáveis varia por harness. Injete a chave por
|
|
31
|
+
um secret manager ou pelo ambiente do processo; não grave a chave no arquivo
|
|
32
|
+
de configuração. Para instalar no projeto Node do próprio harness:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npm install @diegosouzacdv/jev-browser-mcp
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
O padrão `computer` exige Chrome ou Edge instalado. Se optar por `harness`,
|
|
39
|
+
configure `JEV_BROWSER_MODE=harness` antes de instalar o navegador gerenciado
|
|
40
|
+
pelo Playwright com `npx --yes @diegosouzacdv/jev-browser-mcp --install-browser`.
|
|
41
|
+
|
|
42
|
+
O padrão é `computer`: o pacote abre o Chrome/Edge instalado e usa um perfil
|
|
43
|
+
persistente exclusivo em `browser.computer_user_data_dir`. No modo `harness`, a
|
|
44
|
+
instalação baixa uma vez o Chrome/Edge que o Playwright controlará. Personalize
|
|
45
|
+
as escolhas com `JEV_BROWSER_MODE`, `JEV_BROWSER_CHANNEL` e
|
|
46
|
+
`JEV_BROWSER_PROFILE`. O browser permanece aquecido enquanto o processo MCP
|
|
47
|
+
estiver ativo e fecha quando o harness encerra o processo. `JEV_PROVIDER_URL`
|
|
48
|
+
e `JEV_MODEL` podem substituir os valores centrais; a credencial continua no
|
|
49
|
+
nome de variável declarado em `jev.credential_env`.
|
|
50
|
+
|
|
51
|
+
Uma aplicação Node também pode importar `createJevBrowserServer` por
|
|
52
|
+
`@diegosouzacdv/jev-browser-mcp/server` e conectar o servidor ao transporte MCP
|
|
53
|
+
que ela já utiliza.
|
|
54
|
+
|
|
55
|
+
O pacote é montado em uma pasta temporária isolada: o publicador usa o campo
|
|
56
|
+
`files` do `package.json` para copiar somente o código Node, a configuração
|
|
57
|
+
compartilhada e esta documentação, que também vira o `README.md` da raiz do
|
|
58
|
+
pacote. Assim, o npm não inclui o README geral do orquestrador na página do MCP.
|
|
59
|
+
Para gerar e conferir o pacote antes de publicar, execute
|
|
60
|
+
`npm run pack:jev-browser-mcp`; para publicar uma versão já autenticada no npm, execute
|
|
61
|
+
`npm run publish:jev-browser-mcp`. A instalação por npm é a recomendada; use a
|
|
62
|
+
referência GitHub somente quando precisar experimentar uma revisão ainda não
|
|
63
|
+
publicada.
|
|
64
|
+
|
|
65
|
+
### Contrato do pacote
|
|
66
|
+
|
|
67
|
+
O executável oferece `choose_next_action` e `run_browser_flow`, mantém uma
|
|
68
|
+
sessão do browser por processo e reutiliza essa sessão entre chamadas. O plano
|
|
69
|
+
passado ao Jev continua declarativo: clique, preenchimento, seleção nativa,
|
|
70
|
+
hover, espera, teclas aprovadas, asserções por elemento, upload restrito a uma
|
|
71
|
+
raiz local configurada e reações idempotentes a um comentário único. Também
|
|
72
|
+
aceita um nome de iframe para as ações que ocorrem dentro dele. Não aceita
|
|
73
|
+
JavaScript enviado pelo harness, seletores livres nem coordenadas. Ele usa a
|
|
74
|
+
biblioteca Playwright diretamente, sem iniciar um segundo servidor MCP do
|
|
75
|
+
Playwright. O transporte MCP usa `stdio`; toda saída de diagnóstico vai para
|
|
76
|
+
`stderr` para não misturar com JSON-RPC.
|
|
77
|
+
|
|
78
|
+
O modo `computer` grava cookies no perfil exclusivo configurado, e conteúdo da
|
|
79
|
+
página pode conter instruções maliciosas. O Jev recebe a captura acessível com
|
|
80
|
+
uma instrução para tratar esse conteúdo como dado não confiável; não inclua
|
|
81
|
+
segredos no fluxo, no resultado esperado ou nas descrições dos planos.
|
|
82
|
+
|
|
83
|
+
Para um fluxo conhecido, o LLM do harness procura o cenário e os critérios de
|
|
84
|
+
aceitação no projeto e chama `jev_browser.run_browser_flow` com planos
|
|
85
|
+
candidatos declarativos. O MCP abre uma única sessão do Playwright, navega para
|
|
86
|
+
a página inicial, captura o snapshot acessível e pede ao Jev que escolha um
|
|
87
|
+
plano. Em seguida, executa o plano inteiro na mesma sessão e confere o
|
|
88
|
+
resultado esperado na tela.
|
|
89
|
+
|
|
90
|
+
Cada plano pode usar:
|
|
91
|
+
|
|
92
|
+
- `click`, `type`, `hover` e `select_option` com papel/nome acessível exatos;
|
|
93
|
+
- `timeout_seconds` opcional em cada etapa, limitado pela configuração central;
|
|
94
|
+
- `type` com `mode: "keys"` para digitar sequencialmente em campos com máscara,
|
|
95
|
+
`blur: true` para desfocar o campo e `sensitive: false` para permitir que o
|
|
96
|
+
valor apareça nas evidências;
|
|
97
|
+
- `wait_for_text` e `wait_for_condition` (`network_idle`, `hidden` ou
|
|
98
|
+
`text_hidden`);
|
|
99
|
+
- `press_key` com `PageDown`, `PageUp`, `Home`, `End`, `ArrowDown`, `ArrowUp`,
|
|
100
|
+
`Enter`, `Escape` ou `Tab`;
|
|
101
|
+
- `assert_text`, `assert_value`, `assert_visible` e `assert_hidden`; `assert_text`
|
|
102
|
+
pode receber apenas `role` quando a região, como `alert`, não tem nome acessível;
|
|
103
|
+
- `upload_file` para input rotulado, botão que abre o seletor de arquivo ou
|
|
104
|
+
dropzone;
|
|
105
|
+
- `audit_accessibility` com axe-core para WCAG 2.1 A/AA;
|
|
106
|
+
- `like_comment` e `unlike_comment`, que localizam uma linha pelo autor e texto,
|
|
107
|
+
não repetem uma reação já no estado pedido e distinguem `Curtir` de
|
|
108
|
+
`Descurtir`.
|
|
109
|
+
|
|
110
|
+
Quando vários controles têm o mesmo papel e nome, `within` limita a busca a um
|
|
111
|
+
container acessível único, como uma linha ou card. `index` escolhe uma ocorrência
|
|
112
|
+
zero-based dentro desse escopo; sem `index`, o MCP exige exatamente um alvo.
|
|
977
113
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
O modo
|
|
985
|
-
`subprocess` é apenas uma opção local de desenvolvimento.
|
|
986
|
-
|
|
987
|
-
Os gates executam compilação Python, Ruff, mypy, pytest, TypeScript e EXPLAIN
|
|
988
|
-
SQL quando o artefato e o ambiente suportam cada verificação.
|
|
989
|
-
|
|
990
|
-
## 7. HITL HTTP
|
|
991
|
-
|
|
992
|
-
`HITL_API_TOKEN` é um token interno criado pelo operador para proteger a API
|
|
993
|
-
HTTP local. Ele não é fornecido pelo Google, não é `GEMINI_API_KEY`, não é uma
|
|
994
|
-
credencial OAuth do Antigravity e não é um token do Gitea. Mantenha-o fora do
|
|
995
|
-
repositório, prompts, argumentos de processo, logs e artefatos.
|
|
996
|
-
|
|
997
|
-
Em Windows PowerShell, gere um token aleatório sem colar o valor literal no
|
|
998
|
-
histórico. Esta forma funciona também no Windows PowerShell antigo; não troque
|
|
999
|
-
por uma expressão com parênteses ou barras invertidas:
|
|
1000
|
-
|
|
1001
|
-
~~~powershell
|
|
1002
|
-
$bytes = New-Object byte[] 32
|
|
1003
|
-
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
|
|
1004
|
-
|
|
1005
|
-
try {
|
|
1006
|
-
$rng.GetBytes($bytes)
|
|
1007
|
-
}
|
|
1008
|
-
finally {
|
|
1009
|
-
$rng.Dispose()
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"action": "click",
|
|
117
|
+
"role": "button",
|
|
118
|
+
"name": "Add to cart",
|
|
119
|
+
"within": {"role": "group", "name": "Sauce Labs Backpack"}
|
|
1010
120
|
}
|
|
1011
|
-
|
|
1012
|
-
$env:HITL_API_TOKEN = [Convert]::ToBase64String($bytes)
|
|
1013
|
-
Remove-Variable bytes, rng
|
|
1014
|
-
~~~
|
|
1015
|
-
|
|
1016
|
-
A variável vale para a janela/processo atual. Inicie a API na mesma janela,
|
|
1017
|
-
preferencialmente a partir do checkout:
|
|
1018
|
-
|
|
1019
|
-
~~~powershell
|
|
1020
|
-
Set-Location C:\orquestrador
|
|
1021
|
-
& 'C:\orquestrador\.venv\Scripts\python.exe' -m uvicorn server.app:app --host 127.0.0.1 --port 8000
|
|
1022
|
-
~~~
|
|
1023
|
-
|
|
1024
|
-
Se a API rodar como serviço, carregue o mesmo token pelo cofre de segredos do
|
|
1025
|
-
serviço; não coloque o valor diretamente no comando, no arquivo versionado ou
|
|
1026
|
-
em `setx` compartilhado. Para chamar a API em outro terminal, forneça o mesmo
|
|
1027
|
-
valor ao ambiente desse cliente e use apenas o cabeçalho Bearer:
|
|
1028
|
-
|
|
1029
|
-
~~~powershell
|
|
1030
|
-
$headers = @{ Authorization = "Bearer $env:HITL_API_TOKEN" }
|
|
1031
|
-
~~~
|
|
1032
|
-
|
|
1033
|
-
Não execute `$env:HITL_API_TOKEN` sozinho, pois isso imprime o segredo. Ao
|
|
1034
|
-
terminar a sessão, limpe a variável se ela não for mais necessária:
|
|
1035
|
-
|
|
1036
|
-
~~~powershell
|
|
1037
|
-
Remove-Item Env:HITL_API_TOKEN -ErrorAction SilentlyContinue
|
|
1038
|
-
~~~
|
|
1039
|
-
|
|
1040
|
-
Verifique saúde:
|
|
1041
|
-
|
|
1042
|
-
```bash
|
|
1043
|
-
curl http://127.0.0.1:8000/health
|
|
1044
|
-
```
|
|
1045
|
-
|
|
1046
|
-
Consulte ou retome uma thread usando `Authorization: Bearer ...`:
|
|
1047
|
-
|
|
1048
|
-
```bash
|
|
1049
|
-
curl -H "Authorization: Bearer $HITL_API_TOKEN" \
|
|
1050
|
-
http://127.0.0.1:8000/threads/minha-thread/state
|
|
1051
|
-
|
|
1052
|
-
curl -X POST -H "Authorization: Bearer $HITL_API_TOKEN" \
|
|
1053
|
-
-H "Content-Type: application/json" \
|
|
1054
|
-
-d '{"resume_data":{"aprovado":true}}' \
|
|
1055
|
-
http://127.0.0.1:8000/threads/minha-thread/resume
|
|
1056
|
-
```
|
|
1057
|
-
|
|
1058
|
-
### 7.1. Antigravity OAuth: state dir, host e container
|
|
1059
|
-
|
|
1060
|
-
`ORQUESTRADOR_ANTIGRAVITY_STATE_DIR` é o diretório privado onde a API mantém o
|
|
1061
|
-
estado protegido do login Antigravity. Ele não é uma API key, não é o
|
|
1062
|
-
`client_id`, não é o `client_secret` e não deve apontar para o checkout. O
|
|
1063
|
-
diretório pode conter o refresh token; por isso, copiar seus arquivos para
|
|
1064
|
-
prompt, log, issue, artefato ou workspace é uma violação da fronteira de
|
|
1065
|
-
credenciais.
|
|
1066
|
-
|
|
1067
|
-
Há duas visões do mesmo diretório no ambiente local:
|
|
1068
|
-
|
|
1069
|
-
| Onde | Caminho | Uso |
|
|
1070
|
-
|---|---|---|
|
|
1071
|
-
| API no host Windows | `C:\orquestrador-secrets\antigravity-state` | persistência do login e do refresh |
|
|
1072
|
-
| job Linux do runner Docker | `/var/lib/orquestrador/antigravity-state` | caminho usado pelo workflow |
|
|
1073
|
-
| secret do Gitea | `/var/lib/orquestrador/antigravity-state` | valor que o job recebe; não use o caminho Windows |
|
|
1074
|
-
|
|
1075
|
-
O secret do Gitea configura somente o processo do job. Ele não configura uma
|
|
1076
|
-
API que já esteja rodando no host. A API precisa receber o caminho Windows em
|
|
1077
|
-
seu próprio ambiente; o runner precisa montar o mesmo diretório no caminho
|
|
1078
|
-
Linux antes de executar o workflow.
|
|
1079
|
-
|
|
1080
|
-
O diretório deve ser criado e protegido pelo administrador do host, fora de
|
|
1081
|
-
`C:\orquestrador`, com ACL/DACL privada e sem junction, symlink ou reparse
|
|
1082
|
-
point. Para o job Linux, o runner autorizado deve montar esse mesmo diretório
|
|
1083
|
-
como `/var/lib/orquestrador/antigravity-state`; o valor salvo no Gitea é sempre
|
|
1084
|
-
o caminho Linux, nunca o caminho Windows. Não liste nem copie os arquivos de
|
|
1085
|
-
estado OAuth.
|
|
1086
|
-
|
|
1087
|
-
#### 1. Cadastrar o caminho no Gitea
|
|
1088
|
-
|
|
1089
|
-
Em `admin/projeto-global`, abra **Settings → Actions → Secrets** e cadastre
|
|
1090
|
-
somente o par abaixo:
|
|
1091
|
-
|
|
1092
|
-
~~~text
|
|
1093
|
-
Nome: ORQUESTRADOR_ANTIGRAVITY_STATE_DIR
|
|
1094
|
-
Valor: /var/lib/orquestrador/antigravity-state
|
|
1095
|
-
~~~
|
|
1096
|
-
|
|
1097
|
-
Não coloque o caminho Windows nesse secret: o job é Linux dentro do Docker.
|
|
1098
|
-
Não tente ler o valor de volta pela interface e não registre secrets em
|
|
1099
|
-
artefatos. Os secrets `ORQUESTRADOR_ANTIGRAVITY_OAUTH_CLIENT_ID` e
|
|
1100
|
-
`ORQUESTRADOR_ANTIGRAVITY_OAUTH_CLIENT_SECRET` continuam necessários nos
|
|
1101
|
-
passos Kernel/Claude: a conta persistida contém um `refreshToken`, e o proxy
|
|
1102
|
-
precisa desses dois valores para convertê-lo em um access token antes da probe.
|
|
1103
|
-
Eles não são necessários para SDK ou Pi e não devem ser injetados nesses jobs.
|
|
1104
|
-
|
|
1105
|
-
**Não crie um cliente OAuth próprio para esta rota.** Esta instrução existiu
|
|
1106
|
-
aqui até 2026-09-14 e estava errada; ela custou três runs e duas sessões de
|
|
1107
|
-
diagnóstico, e o sintoma não apontava para ela.
|
|
1108
|
-
|
|
1109
|
-
Um cliente criado no seu projeto do Google Cloud **autentica com sucesso** — o
|
|
1110
|
-
login abre, o consentimento passa, o `refresh_token` é emitido e renovado — e
|
|
1111
|
-
mesmo assim toda chamada morre em `UPSTREAM_HTTP_403`. O Google emite token
|
|
1112
|
-
para qualquer cliente válido; o backend do Antigravity só aceita token cunhado
|
|
1113
|
-
pelo cliente em que ele confia. A falha aparece apenas na chamada real, muito
|
|
1114
|
-
depois do login, e se parece com um problema de conta ou de modelo.
|
|
1115
|
-
|
|
1116
|
-
O par que a rota exige é o que o snapshot upstream traz embutido, visível nas
|
|
1117
|
-
linhas removidas de `antigravity_proxy/patches/003-account-boundary.patch`. O
|
|
1118
|
-
patch o tirou do **default de runtime** para que uma cópia do sidecar não fosse
|
|
1119
|
-
fonte de credencial; o valor continua no repositório, dentro do patch, e é dali
|
|
1120
|
-
que ele deve ser lido para preencher as duas variáveis:
|
|
1121
|
-
|
|
1122
|
-
~~~text
|
|
1123
|
-
ORQUESTRADOR_ANTIGRAVITY_OAUTH_CLIENT_ID
|
|
1124
|
-
ORQUESTRADOR_ANTIGRAVITY_OAUTH_CLIENT_SECRET
|
|
1125
|
-
~~~
|
|
1126
|
-
|
|
1127
|
-
Isso vale para o ambiente da API **e** para os secrets do Gitea: os dois lados
|
|
1128
|
-
precisam do mesmo cliente, porque o refresh token fica preso ao cliente que o
|
|
1129
|
-
emitiu. Trocar o cliente invalida a conta persistida — é obrigatório
|
|
1130
|
-
`logout` seguido de `auto --force-login` depois de mudar esses valores.
|
|
1131
|
-
|
|
1132
|
-
Como confirmar em segundos, sem gastar uma corrida da matriz:
|
|
1133
|
-
|
|
1134
|
-
~~~powershell
|
|
1135
|
-
Set-Location C:\orquestrador
|
|
1136
|
-
$env:PYTHONPATH = "."
|
|
1137
|
-
& '.\.venv\Scripts\python.exe' scripts\list_antigravity_models.py
|
|
1138
|
-
~~~
|
|
1139
|
-
|
|
1140
|
-
Com o cliente certo, isso lista os modelos que a conta anuncia — em 2026-09-14,
|
|
1141
|
-
onze, incluindo `gemini-3.8-flash-tiered`, que é o default do workflow. Com o
|
|
1142
|
-
cliente errado, responde `500 ... UPSTREAM_HTTP_403`, e nenhuma troca de conta
|
|
1143
|
-
ou de modelo muda esse resultado.
|
|
1144
|
-
|
|
1145
|
-
#### 2. Login automático
|
|
1146
|
-
|
|
1147
|
-
Depois de configurar no ambiente da API `ORQUESTRADOR_ANTIGRAVITY_STATE_DIR`,
|
|
1148
|
-
`ORQUESTRADOR_ANTIGRAVITY_OAUTH_CLIENT_ID` e
|
|
1149
|
-
`ORQUESTRADOR_ANTIGRAVITY_OAUTH_CLIENT_SECRET`, execute somente o helper abaixo
|
|
1150
|
-
no host autorizado:
|
|
1151
|
-
|
|
1152
|
-
~~~powershell
|
|
1153
|
-
Set-Location C:\orquestrador
|
|
1154
|
-
& 'C:\orquestrador\.venv\Scripts\python.exe' scripts\antigravity_auth.py auto
|
|
1155
|
-
~~~
|
|
1156
|
-
|
|
1157
|
-
O helper valida o ambiente, abre o navegador, recebe o callback loopback sem
|
|
1158
|
-
clipboard ou impressão de código e executa `refresh`. Se já houver uma conta,
|
|
1159
|
-
ele não abre novo login. O resultado esperado é `ready`; a API grava o estado
|
|
1160
|
-
protegido somente no state dir. Não execute outro fluxo OAuth em paralelo.
|
|
1161
|
-
|
|
1162
|
-
O valor produzido depois do login é estado protegido da conta, não deve ser
|
|
1163
|
-
usado como `ORQUESTRADOR_ANTIGRAVITY_OAUTH_CLIENT_SECRET` nem copiado para
|
|
1164
|
-
prompt, log, issue ou artefato.
|
|
1165
|
-
|
|
1166
|
-
Somente depois de `status` pronto, refresh aprovado, DACL/reparse verificados e
|
|
1167
|
-
mount confirmado deve ser disparada a matriz real. O procedimento detalhado de
|
|
1168
|
-
AGS-004/ACP está em
|
|
1169
|
-
[`docs/ags004-acp-live-runbook.md`](docs/ags004-acp-live-runbook.md).
|
|
1170
|
-
|
|
1171
|
-
## 8. Testes locais
|
|
1172
|
-
|
|
1173
|
-
```bash
|
|
1174
|
-
pytest -q
|
|
1175
|
-
ruff check .
|
|
1176
|
-
mypy agents src server main.py scripts pi_harness mcp_servers
|
|
1177
|
-
python scripts/smoke/mcp_tools_binding.py
|
|
1178
|
-
python scripts/smoke/full_orchestrator_e2e.py
|
|
1179
|
-
```
|
|
1180
|
-
|
|
1181
|
-
Smoke real Groq, com limite baixo de tokens:
|
|
1182
|
-
|
|
1183
|
-
```bash
|
|
1184
|
-
GROQ_API_KEY="$GROQ_API_KEY" ORQUESTRADOR_SMOKE_MAX_TOKENS=512 \
|
|
1185
|
-
ORQUESTRADOR_SMOKE_RUN_GRAPH=false \
|
|
1186
|
-
python scripts/smoke_groq.py
|
|
1187
|
-
```
|
|
1188
|
-
|
|
1189
|
-
O smoke não imprime a chave. O prompt caching é automático na Groq; o teste
|
|
1190
|
-
registra `cached_tokens`, mas um cache miss não é tratado como falha porque a
|
|
1191
|
-
Groq não garante hit em toda chamada. O workflow manual aprovado percorre
|
|
1192
|
-
Compound, Compound Mini, Prompt Guard, GPT-OSS 20B, GPT-OSS 120B, Qwen 27B e
|
|
1193
|
-
Safeguard 20B; todas as chamadas retornaram `OK`. O grafo fica desativado nesse
|
|
1194
|
-
workflow para separar smoke de modelos de gates HITL/Compliance.
|
|
1195
|
-
|
|
1196
|
-
## 9. Batch API
|
|
1197
|
-
|
|
1198
|
-
Ative somente para workloads assíncronos:
|
|
1199
|
-
|
|
1200
|
-
```dotenv
|
|
1201
|
-
ORQUESTRADOR_BATCH_ENABLED=true
|
|
1202
|
-
ORQUESTRADOR_USE_BATCH=true
|
|
1203
|
-
```
|
|
1204
|
-
|
|
1205
|
-
O adapter usa JSONL e a API Groq Batch. A janela é assíncrona; acompanhe o
|
|
1206
|
-
`batch_id` e não coloque prompts completos no `GraphState`.
|
|
1207
|
-
|
|
1208
|
-
O Batch depende de habilitação do plano Groq. Se a API responder
|
|
1209
|
-
`403 not_available_for_plan`, a credencial está válida, mas a conta ainda não
|
|
1210
|
-
possui esse recurso; o adapter registra a situação e usa uma simulação offline
|
|
1211
|
-
correlacionável para testes locais. A execução real exige habilitar o Batch no
|
|
1212
|
-
plano Groq e usar um modelo elegível.
|
|
1213
|
-
|
|
1214
|
-
Documentação: [Groq Batch API](https://console.groq.com/docs/batch) e
|
|
1215
|
-
[referência de batches](https://console.groq.com/docs/api-reference).
|
|
1216
|
-
|
|
1217
|
-
## 10. Git remoto e GitHub Checks
|
|
1218
|
-
|
|
1219
|
-
Em GitHub Actions, configure secrets/variáveis:
|
|
1220
|
-
|
|
1221
|
-
```dotenv
|
|
1222
|
-
GITHUB_TOKEN=provided-by-github-actions
|
|
1223
|
-
GITHUB_REPOSITORY=owner/repository
|
|
1224
|
-
GITHUB_SHA=commit-sha
|
|
1225
|
-
ORQUESTRADOR_GIT_REMOTE=https://github.com/owner/repository.git
|
|
1226
|
-
```
|
|
1227
|
-
|
|
1228
|
-
O workflow cria branch por thread, publica commit, abre PR draft e publica
|
|
1229
|
-
GitHub Check. Sem credencial explícita, o sistema mantém apenas branch/commit
|
|
1230
|
-
local e não inventa URL de PR.
|
|
1231
|
-
|
|
1232
|
-
## 11. CI
|
|
1233
|
-
|
|
1234
|
-
O workflow `quality.yml` é o portão determinístico: instalação, Ruff, mypy,
|
|
1235
|
-
pytest, os smokes e o build do sandbox. Ele é **dividido em dois jobs** —
|
|
1236
|
-
`quality` roda `-m 'not slow and not quarantine'` e `slow-e2e` roda o
|
|
1237
|
-
complemento —, e os dois somados são `pytest -q`.
|
|
1238
|
-
|
|
1239
|
-
**Quando ele roda está nos próprios arquivos, e as duas superfícies diferem de
|
|
1240
|
-
propósito.** No GitHub, `pull_request` e disparo manual; no Gitea, disparo
|
|
1241
|
-
manual apenas, porque enfileirar um check obrigatório contra um runner local
|
|
1242
|
-
que nem sempre está ligado seria pior que não ter. Esta frase dizia "em todo
|
|
1243
|
-
push/PR" até 2026-09-15, o que estava errado nas duas — e é o tipo de cópia em
|
|
1244
|
-
prosa que `tests/guards/test_governance_document_truth_guard.py` passou a
|
|
1245
|
-
conferir contra o YAML.
|
|
1246
|
-
|
|
1247
|
-
Ele **não bloqueia merge** em nenhuma das duas: o `master` do Gitea não tem
|
|
1248
|
-
regra de proteção, e a do GitHub é recurso pago em repositório privado. O que
|
|
1249
|
-
segura a qualidade antes do push é a regra de trabalho do `AGENTS.md`, aplicada
|
|
1250
|
-
pelo hook de pre-commit; o workflow é a repetição disso num Linux, que é o
|
|
1251
|
-
ambiente que a máquina do operador não tem.
|
|
1252
|
-
|
|
1253
|
-
O workflow `groq-smoke.yml` é manual (`workflow_dispatch`) e exige o secret
|
|
1254
|
-
`GROQ_API_KEY` em um GitHub Environment protegido. Ele não deve ser executado
|
|
1255
|
-
em PRs de forks.
|
|
1256
|
-
|
|
1257
|
-
Para executar o E2E do grafo com LLM real, use o workflow manual
|
|
1258
|
-
`.github/workflows/groq-graph-e2e.yml`. Ele usa o mesmo Environment protegido,
|
|
1259
|
-
executa as trilhas `new`, `feature` e `fix` com Groq e mantém os limites do tier
|
|
1260
|
-
on-demand. O workflow define `ORQUESTRADOR_REQUIRE_REAL_LLM=true`, portanto
|
|
1261
|
-
fallback offline faz o job falhar. A execução pode terminar com
|
|
1262
|
-
`compliance_status=rejected` por
|
|
1263
|
-
pendências legítimas; o objetivo desse workflow é comprovar o caminho real do
|
|
1264
|
-
grafo, não forçar aprovação de uma entrega sem evidência.
|
|
1265
|
-
|
|
1266
|
-
No GitHub: **Actions → groq-graph-e2e → Run workflow → master**. Se o
|
|
1267
|
-
Environment exigir aprovação, aprove o job antes que os secrets sejam
|
|
1268
|
-
liberados.
|
|
1269
|
-
|
|
1270
|
-
Permissões mínimas recomendadas:
|
|
1271
|
-
|
|
1272
|
-
```yaml
|
|
1273
|
-
permissions:
|
|
1274
|
-
contents: read
|
|
1275
|
-
checks: write
|
|
1276
|
-
```
|
|
1277
|
-
|
|
1278
|
-
Para PR remoto, conceda `contents: write` e `pull-requests: write` apenas no
|
|
1279
|
-
workflow manual protegido.
|
|
1280
|
-
|
|
1281
|
-
## 12. Observabilidade
|
|
1282
|
-
|
|
1283
|
-
Para exportar spans via OTLP:
|
|
1284
|
-
|
|
1285
|
-
```dotenv
|
|
1286
|
-
OTEL_SERVICE_NAME=orquestrador
|
|
1287
|
-
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
|
|
1288
|
-
```
|
|
1289
|
-
|
|
1290
|
-
Sem endpoint, o sistema registra apenas eventos técnicos locais. Prompts,
|
|
1291
|
-
código, tokens de autenticação e conteúdo de documentação não entram nos
|
|
1292
|
-
traces.
|
|
1293
|
-
|
|
1294
|
-
## 13. Artefatos e D5
|
|
1295
|
-
|
|
1296
|
-
O código gerado é salvo em `workspace/<thread-id>/`; o estado carrega hashes e
|
|
1297
|
-
referências do manifesto. Checkpoints legados ainda podem ser lidos durante a
|
|
1298
|
-
migração, mas novas execuções não precisam persistir blobs completos quando
|
|
1299
|
-
`ORQUESTRADOR_LEGACY_CODE_STATE=false`.
|
|
1300
|
-
|
|
1301
|
-
## 14. Documentação do projeto
|
|
1302
|
-
|
|
1303
|
-
- [Trilha de sessões e status](docs/plano-agentic-skills-status.md) (append-only, com índice em `docs/plano-status.json`)
|
|
1304
|
-
- [Estrutura de pastas e nomenclatura](docs/planos/auditoria/estrutura-pastas-e-nomenclatura.md)
|
|
1305
|
-
- [Fluxo de agentes, documentação e execução](docs/fluxo-agentes-documentacao-e-execucao.md)
|
|
1306
|
-
- [Guia de atualização dos validadores](docs/planos/auditoria/guia-atualizacao-versoes-validadores.md)
|
|
1307
|
-
|
|
1308
|
-
## 15. Time travel, retenção e governança
|
|
1309
|
-
|
|
1310
|
-
Forks são não destrutivos: o checkpoint pai permanece na thread original e o
|
|
1311
|
-
fork recebe novo `thread_id`, workspace independente e branch local.
|
|
1312
|
-
|
|
1313
|
-
```bash
|
|
1314
|
-
python scripts/time_travel.py list --thread-id <id>
|
|
1315
|
-
python scripts/time_travel.py fork --thread-id <id> --checkpoint-id <cp> \
|
|
1316
|
-
--field prazo_entrega --value "2026-09-15"
|
|
1317
|
-
python scripts/time_travel.py retention --days 30 # dry-run
|
|
1318
|
-
python scripts/time_travel.py retention --days 30 --purge --legal-hold <id>
|
|
1319
121
|
```
|
|
1320
122
|
|
|
1321
|
-
Objetos D5 são endereçados por SHA-256 e o modo legado permanece opt-in:
|
|
1322
|
-
`ORQUESTRADOR_LEGACY_CODE_STATE=true`.
|
|
1323
|
-
|
|
1324
|
-
## 16. Web escopada, qualidade e Batch
|
|
1325
|
-
|
|
1326
|
-
Configure `ORQUESTRADOR_WEB_ALLOWLIST` antes de habilitar o MCP web; sem
|
|
1327
|
-
allowlist, HTTPS, resolução pública e aprovação humana, o conteúdo é
|
|
1328
|
-
bloqueado. Para quality gates completos, use `docker/Dockerfile.quality`, que instala
|
|
1329
|
-
Playwright/Lighthouse e grava `data/quality-evidence.json`.
|
|
1330
|
-
|
|
1331
|
-
Submissão/consulta Batch:
|
|
1332
|
-
|
|
1333
|
-
```bash
|
|
1334
|
-
python scripts/batch_cli.py submit --prompt "health" --model deepseek/deepseek-v4-flash-0731
|
|
1335
|
-
python scripts/batch_cli.py status <batch-id>
|
|
1336
|
-
```
|
|
1337
|
-
|
|
1338
|
-
O workflow `.github/workflows/batch-submit.yml` usa somente modelos Groq Batch
|
|
1339
|
-
elegíveis e um GitHub Environment protegido.
|
|
1340
|
-
|
|
1341
|
-
## 17. OpenRouter opcional (multi-provedor e multimodal)
|
|
1342
|
-
|
|
1343
|
-
Groq continua sendo o padrão. Para habilitar OpenRouter, configure a chave
|
|
1344
|
-
somente no ambiente de execução (nunca em `GraphState`, registry ou logs):
|
|
1345
|
-
|
|
1346
|
-
```dotenv
|
|
1347
|
-
ORQUESTRADOR_LLM_PROVIDER=openrouter
|
|
1348
|
-
OPENROUTER_API_KEY=...
|
|
1349
|
-
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
|
|
1350
|
-
# opcional: OPENROUTER_HTTP_REFERER=https://seu-dominio
|
|
1351
|
-
# opcional: OPENROUTER_TITLE=Orquestrador
|
|
1352
|
-
# opcional: ORQUESTRADOR_OPENROUTER_TIMEOUT_SECONDS=90
|
|
1353
|
-
# compatibilidade legada, somente com ORQUESTRADOR_POLICY_LEGACY_OVERRIDES=true
|
|
1354
|
-
# ORQUESTRADOR_OPENROUTER_REASONING_EFFORT=low
|
|
1355
|
-
# ORQUESTRADOR_OPENROUTER_STRUCTURED_METHOD=json_schema
|
|
1356
|
-
# ORQUESTRADOR_OPENROUTER_REQUIRE_PARAMETERS=true
|
|
1357
|
-
# ORQUESTRADOR_OPENROUTER_STRUCTURED_RETRIES=1
|
|
1358
|
-
# opcional: ORQUESTRADOR_SMOKE_MAX_COMPLETION_TOKENS=16
|
|
1359
|
-
# opcional: ORQUESTRADOR_LLM_LOG_PREVIEW_CHARS=120 (0 desabilita previews)
|
|
1360
|
-
```
|
|
1361
|
-
|
|
1362
|
-
Para modelos marcados no catálogo local como compatíveis, o padrão é
|
|
1363
|
-
`json_schema` com `strict=true`. No E2E canônico,
|
|
1364
|
-
`ORQUESTRADOR_OPENROUTER_REQUIRE_PARAMETERS=true` também restringe o
|
|
1365
|
-
roteamento a endpoints que aceitem os parâmetros enviados. Use `json_mode`
|
|
1366
|
-
somente como override para um modelo/endpoint sem structured outputs nativos.
|
|
1367
|
-
|
|
1368
|
-
As chamadas diretas de OpenRouter (catálogo, mídia, TTS/STT, vídeo e smoke)
|
|
1369
|
-
usam o pacote oficial `openrouter==1.1.81`, com respostas tipadas e clientes
|
|
1370
|
-
síncrono/assíncrono. O LangGraph permanece sobre LangChain para preservar
|
|
1371
|
-
`Runnable`, saída estruturada, callbacks, ferramentas e o roteamento entre
|
|
1372
|
-
Groq, OpenRouter e demais provedores. O Batch é a única exceção: nessa versão
|
|
1373
|
-
do SDK o endpoint `/beta/batches` ainda não está exposto, portanto fica em um
|
|
1374
|
-
transporte HTTP privado e isolado.
|
|
1375
|
-
|
|
1376
|
-
O SDK não realiza novas tentativas por conta própria. Backoff, circuit breaker,
|
|
1377
|
-
limite de quatro tentativas e fallback continuam centralizados no orquestrador.
|
|
1378
|
-
O adapter aceita `max_tokens` apenas por compatibilidade e o converte para
|
|
1379
|
-
`max_completion_tokens`; código novo deve usar o segundo nome.
|
|
1380
|
-
|
|
1381
|
-
Cada chamada OpenRouter também emite logs reduzidos `[OPENROUTER] REQUEST`,
|
|
1382
|
-
`RESPONSE` e `ERROR`, com agente, modelo, tentativa, duração, hashes,
|
|
1383
|
-
tamanhos, tokens/cache, generation ID e erro sanitizado. Prompts e respostas
|
|
1384
|
-
completos não são gravados; o preview é limitado por
|
|
1385
|
-
`ORQUESTRADOR_LLM_LOG_PREVIEW_CHARS` (defina `0` em ambientes sensíveis).
|
|
1386
|
-
|
|
1387
|
-
Os limites físicos de cada modelo permanecem em `src/llm/llm_model_config.py`. Já
|
|
1388
|
-
os orçamentos operacionais por agente ficam em um único JSON:
|
|
1389
|
-
|
|
1390
|
-
```dotenv
|
|
1391
|
-
# Somente para migração de ambientes antigos; a fonte normal é
|
|
1392
|
-
# config/agent-policy.json e o mapa é ignorado sem opt-in explícito.
|
|
1393
|
-
ORQUESTRADOR_POLICY_LEGACY_OVERRIDES=true
|
|
1394
|
-
ORQUESTRADOR_AGENT_RUNTIME_JSON={"agent_clean_code":{"max_completion_tokens":8192,"reasoning_effort":"minimal","timeout_seconds":180,"structured_retries":1},"agent_code_review":{"max_completion_tokens":8192,"reasoning_effort":"minimal","timeout_seconds":180,"structured_retries":0}}
|
|
1395
|
-
ORQUESTRADOR_REVIEW_MAX_INPUT_CHARS=30000
|
|
1396
|
-
ORQUESTRADOR_RED_TEAM_MAX_INPUT_CHARS=30000
|
|
1397
|
-
```
|
|
1398
|
-
|
|
1399
|
-
Falhas do Planejador ou de Requisitos são bloqueantes. Primeiro,
|
|
1400
|
-
`structured_retries` repete apenas a chamada do agente. Se todas as tentativas
|
|
1401
|
-
falharem, o portão `critical_failure_gate` permite repetir somente esse nó a
|
|
1402
|
-
partir do checkpoint ou encerrar. `ORQUESTRADOR_CRITICAL_NODE_RETRIES` limita
|
|
1403
|
-
essas repetições manuais (padrão: `1`). Em CI, `auto_aprovar` escolhe encerrar;
|
|
1404
|
-
documentação ausente nunca é transformada em fallback aprovado.
|
|
1405
|
-
|
|
1406
|
-
Planejador e Consultor persistem seus resultados antes dos nós de aprovação.
|
|
1407
|
-
Assim, retomar um HITL não repete a chamada LLM. Clean Code, Code Review, Red
|
|
1408
|
-
Team e Compliance usam contratos compactos; fallback de um quality gate é
|
|
1409
|
-
registrado em `validacoes_fallback`, reprova o Compliance e faz o E2E real
|
|
1410
|
-
falhar explicitamente.
|
|
1411
|
-
|
|
1412
|
-
O dispatcher é orientado por fases: o roadmap normalizado atribui os agentes
|
|
1413
|
-
de implementação e o grafo executa `agent_dba` (persistência/contrato),
|
|
1414
|
-
`agent_backend` (API) e `agent_frontend` (interface) em etapas separadas,
|
|
1415
|
-
com fan-in e aprovação HITL entre elas. Code Review e Red Team só começam após
|
|
1416
|
-
a última fase; threads antigas sem a atribuição `agentes` mantêm o fan-out
|
|
1417
|
-
legado para compatibilidade.
|
|
1418
|
-
|
|
1419
|
-
O workflow `openrouter-graph-e2e.yml` define
|
|
1420
|
-
`ORQUESTRADOR_ITERATIVE_REPAIR=false`: ele valida uma rodada completa e
|
|
1421
|
-
encaminha achados ao Compliance sem repetir três ciclos de geração, que
|
|
1422
|
-
custariam três vezes mais para dizer a mesma coisa. O padrão está em
|
|
1423
|
-
`defaults.iterative_repair` e a variável o sobrepõe para uma corrida.
|
|
1424
|
-
|
|
1425
|
-
Antes isso era `ORQUESTRADOR_E2E_NO_REPAIR`, lida com literal dentro de
|
|
1426
|
-
`src/state/graph.py`. É deliberadamente independente de
|
|
1427
|
-
`ORQUESTRADOR_RUN_MODE`: amarrar as duas faria quem quer uma corrida mais
|
|
1428
|
-
barata pinar o provedor sem pedir, e tornaria impossível medir o próprio laço
|
|
1429
|
-
de reparação.
|
|
1430
|
-
|
|
1431
|
-
Rotas podem ser escolhidas por agente/perfil/thread em
|
|
1432
|
-
`ORQUESTRADOR_LLM_ROUTING_JSON`; `fallback_models` usa a cascata nativa do
|
|
1433
|
-
OpenRouter e `external_routes` define a cascata entre provedores. O limite é
|
|
1434
|
-
de quatro tentativas e erros de autenticação, payload, orçamento e segurança
|
|
1435
|
-
não fazem fallback.
|
|
1436
|
-
|
|
1437
|
-
A precedência é `thread/agente > thread/perfil > projeto/agente >
|
|
1438
|
-
projeto/perfil > defaults do projeto > ambiente`. O registry persiste apenas
|
|
1439
|
-
JSON de política; chaves permanecem exclusivamente em variáveis de ambiente.
|
|
1440
|
-
|
|
1441
|
-
### Claude Code como harness opcional
|
|
1442
|
-
|
|
1443
|
-
É possível adicionar o Claude Code/Anthropic Agent SDK como backend de
|
|
1444
|
-
execução dos agentes de código, mantendo a OpenRouter como gateway. A
|
|
1445
|
-
arquitetura recomendada é preservar o LangGraph como plano de controle
|
|
1446
|
-
(roteamento, HITL, orçamento, checkpoints, Compliance e D5) e delegar apenas
|
|
1447
|
-
uma tarefa de implementação isolada ao harness Claude Code, com workspace e
|
|
1448
|
-
tools permitidas explicitamente.
|
|
1449
|
-
|
|
1450
|
-
O processo filho receberá as credenciais somente em memória:
|
|
1451
|
-
|
|
1452
|
-
```dotenv
|
|
1453
|
-
ANTHROPIC_BASE_URL=https://openrouter.ai/api
|
|
1454
|
-
ANTHROPIC_AUTH_TOKEN=<valor de OPENROUTER_API_KEY injetado no processo>
|
|
1455
|
-
ANTHROPIC_API_KEY=
|
|
1456
|
-
```
|
|
1457
|
-
|
|
1458
|
-
O executor está disponível por política explícita. O workflow canônico usa
|
|
1459
|
-
`deepseek/deepseek-v4-flash-0731` através do protocolo Anthropic Skin. A
|
|
1460
|
-
documentação da OpenRouter garante formalmente o Claude Code apenas com
|
|
1461
|
-
modelos Anthropic first-party; DeepSeek, MiniMax e Kimi são portanto uma rota
|
|
1462
|
-
experimental do projeto e precisam de E2E próprio. A política de produção é:
|
|
1463
|
-
|
|
1464
123
|
```json
|
|
1465
|
-
{"
|
|
1466
|
-
```
|
|
1467
|
-
|
|
1468
|
-
MiniMax M3 custa US$ 0,30/M de input e US$ 1,20/M de output; Kimi K2.7
|
|
1469
|
-
Code custa US$ 0,67/M de input e US$ 3,40/M de output (valores de catálogo,
|
|
1470
|
-
o custo efetivo vem do usage). Esses fallbacks só são habilitados quando
|
|
1471
|
-
`ORQUESTRADOR_EXECUTOR_FALLBACK_MODE=production`; workflows usam
|
|
1472
|
-
`disabled`, DeepSeek exclusivamente e no máximo três tentativas. Erros de
|
|
1473
|
-
payload, autenticação, orçamento, segurança e ownership nunca fazem fallback.
|
|
1474
|
-
|
|
1475
|
-
Documentação oficial: [Quickstart](https://openrouter.ai/docs/quickstart),
|
|
1476
|
-
[Client SDKs](https://openrouter.ai/docs/client-sdks/overview),
|
|
1477
|
-
[SDK Python](https://openrouter.ai/docs/client-sdks/python/overview),
|
|
1478
|
-
[model fallbacks](https://openrouter.ai/docs/guides/routing/model-fallbacks),
|
|
1479
|
-
[multimodal](https://openrouter.ai/docs/guides/overview/multimodal/overview),
|
|
1480
|
-
[Batch](https://openrouter.ai/docs/batch-quickstart),
|
|
1481
|
-
[prompt caching](https://openrouter.ai/docs/guides/best-practices/prompt-caching#caching-in-the-batch-api)
|
|
1482
|
-
e [MCP administrativo](https://openrouter.ai/docs/guides/overview/mcp-server).
|
|
1483
|
-
Para o harness, consulte [Claude Code com OpenRouter](https://openrouter.ai/docs/cookbook/coding-agents/claude-code-integration)
|
|
1484
|
-
e [Anthropic Agent SDK com OpenRouter](https://openrouter.ai/docs/guides/community/anthropic-agent-sdk).
|
|
1485
|
-
|
|
1486
|
-
Workflows manuais separados:
|
|
1487
|
-
|
|
1488
|
-
```text
|
|
1489
|
-
.github/workflows/openrouter-smoke.yml
|
|
1490
|
-
.github/workflows/openrouter-graph-e2e.yml
|
|
1491
|
-
.github/workflows/openrouter-batch-submit.yml
|
|
1492
|
-
.github/workflows/openrouter-batch-status.yml
|
|
124
|
+
{"action":"click","role":"button","name":"Add to cart","index":0}
|
|
1493
125
|
```
|
|
1494
126
|
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
1. Crie uma chave em [OpenRouter Keys](https://openrouter.ai/settings/keys).
|
|
1498
|
-
2. Em **Settings → Environments**, crie exatamente `openrouter-smoke`,
|
|
1499
|
-
`openrouter-graph-e2e` e `openrouter-batch`.
|
|
1500
|
-
3. Em cada Environment, adicione o secret **`OPENROUTER_API_KEY`**. Não use
|
|
1501
|
-
Repository variable nem coloque a chave no YAML. Se o Environment exigir
|
|
1502
|
-
aprovação, aprove a implantação antes da execução.
|
|
1503
|
-
4. Opcionalmente adicione as variables `OPENROUTER_HTTP_REFERER` e
|
|
1504
|
-
`OPENROUTER_TITLE` para identificação da aplicação.
|
|
1505
|
-
5. Em **Actions → workflow → Run workflow**, execute primeiro o smoke. Depois
|
|
1506
|
-
use o graph E2E. Batch exige quota/plano habilitado; submeta o lote e copie
|
|
1507
|
-
o `batch_id` impresso para o workflow de status.
|
|
1508
|
-
|
|
1509
|
-
Pela CLI do GitHub:
|
|
1510
|
-
|
|
1511
|
-
```bash
|
|
1512
|
-
gh secret set OPENROUTER_API_KEY --env openrouter-smoke --repo OWNER/REPO
|
|
1513
|
-
gh secret set OPENROUTER_API_KEY --env openrouter-graph-e2e --repo OWNER/REPO
|
|
1514
|
-
gh secret set OPENROUTER_API_KEY --env openrouter-batch --repo OWNER/REPO
|
|
1515
|
-
gh workflow run openrouter-smoke.yml --ref master -f model=deepseek/deepseek-v4-flash-0731
|
|
1516
|
-
gh run watch --exit-status
|
|
1517
|
-
```
|
|
1518
|
-
|
|
1519
|
-
O grafo E2E executa uma trilha por vez por padrão, reduzindo rate limit e
|
|
1520
|
-
duração. Escolha a trilha assim:
|
|
1521
|
-
|
|
1522
|
-
A trilha `new` do workflow usa deliberadamente o pedido fixo e pequeno
|
|
1523
|
-
`CRUD simples de produtos (código identificador e nome)`, para validar o
|
|
1524
|
-
fluxo do grafo e o Compliance sem transformar o teste em uma geração de
|
|
1525
|
-
produto grande. Ele pode ser sobrescrito apenas por `ORQUESTRADOR_E2E_CLIENT_INPUT`
|
|
1526
|
-
em execuções controladas.
|
|
127
|
+
### Captura de downloads
|
|
1527
128
|
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
-f model=deepseek/deepseek-v4-flash-0731 -f tracks=new
|
|
1537
|
-
# Para executar todas: -f tracks=new,feature,fix
|
|
1538
|
-
```
|
|
1539
|
-
|
|
1540
|
-
O primeiro passo de cada workflow executa `test -n "$OPENROUTER_API_KEY"`; se
|
|
1541
|
-
falhar, o secret está no Environment errado, o Environment não foi aprovado ou
|
|
1542
|
-
o nome está diferente. A API usa `https://openrouter.ai/api/v1`, conforme o
|
|
1543
|
-
[Quickstart oficial](https://openrouter.ai/docs/quickstart), e os modelos
|
|
1544
|
-
devem ser IDs publicados no [catálogo](https://openrouter.ai/models).
|
|
1545
|
-
Para contas com crédito limitado, reduza opcionalmente
|
|
1546
|
-
`ORQUESTRADOR_OPENROUTER_MAX_COMPLETION_TOKENS`; quando ausente, cada modelo
|
|
1547
|
-
usa o limite registrado em `src/llm/llm_model_config.py`. Os workflows OpenRouter
|
|
1548
|
-
usam uma cascata paga de DeepSeek V4 Flash e Solar Pro4, evitando as cotas
|
|
1549
|
-
instáveis dos endpoints gratuitos.
|
|
1550
|
-
|
|
1551
|
-
O smoke registra modelo efetivo, generation ID, tokens, cache, custo e BYOK.
|
|
1552
|
-
O smoke usa `ORQUESTRADOR_SMOKE_MAX_COMPLETION_TOKENS` (16 por padrão, apenas
|
|
1553
|
-
para manter a chamada curta); `ORQUESTRADOR_SMOKE_MAX_TOKENS` continua aceito
|
|
1554
|
-
temporariamente. Ele não limita as execuções do grafo. No Batch, `max_tokens`
|
|
1555
|
-
é opcional por item: se omitido, o adapter não injeta um teto artificial do
|
|
1556
|
-
OpenRouter; informe-o somente quando quiser limitar aquele item.
|
|
1557
|
-
Batch OpenRouter aceita apenas texto, um modelo por lote e janela de 24 horas;
|
|
1558
|
-
simulação offline só é permitida com
|
|
1559
|
-
`ORQUESTRADOR_BATCH_OFFLINE_SIMULATION=true`.
|
|
1560
|
-
|
|
1561
|
-
Uploads multimodais são armazenados localmente por SHA-256 em D5. Imagem,
|
|
1562
|
-
PDF, áudio e vídeo são convertidos para as partes da API apenas durante a
|
|
1563
|
-
requisição; base64 e binários não entram em checkpoints. As APIs prontas para
|
|
1564
|
-
frontend são `/llm/*`, `/integrations/openrouter/mcp/*`, `/media/*`,
|
|
1565
|
-
`/batches` e `POST /threads`. Para OAuth administrativo por usuário configure
|
|
1566
|
-
`OIDC_ISSUER_URL`, `OIDC_AUDIENCE`, `OIDC_JWKS_URL` e
|
|
1567
|
-
`ORQUESTRADOR_CREDENTIAL_ENCRYPTION_KEY`.
|
|
1568
|
-
|
|
1569
|
-
## Planejamento especializado e executores agentic
|
|
1570
|
-
|
|
1571
|
-
O grafo separa planejamento e execução: requisitos, arquitetura e Clean Code
|
|
1572
|
-
alimentam os planos DBA, Backend e Frontend; uma validação determinística
|
|
1573
|
-
confere cobertura/propriedade; uma única aprovação HITL libera a ordem DBA →
|
|
1574
|
-
Backend → Frontend. Backend não pode possuir migrations e Frontend não pode
|
|
1575
|
-
inventar endpoint, SQL ou regra de negócio. Migrations que falham no banco
|
|
1576
|
-
efêmero não entram no manifesto D5.
|
|
1577
|
-
|
|
1578
|
-
O executor padrão é o **`kernel`**, declarado em `config/agent-policy.json`
|
|
1579
|
-
(`defaults.executor`). Os executores selecionáveis são `kernel`, `claude_code`,
|
|
1580
|
-
`pi` e `antigravity_sdk` (`defaults.selectable.executor`); `legacy` (o adapter
|
|
1581
|
-
LangChain) e `deepseek_harness` permanecem apenas como compatibilidade, em
|
|
1582
|
-
`COMPATIBILITY_EXECUTOR_NAMES` de `src/execution/executors.py`, e não são o
|
|
1583
|
-
padrão de nada. Claude Code pode ser ativado por política para Backend/Frontend
|
|
1584
|
-
e usa o SDK `claude-agent-sdk` com OpenRouter/Anthropic Skin. DeepSeek Harness é
|
|
1585
|
-
somente shadow/piloto em Linux, workspace isolado e sem aplicar artefatos ao
|
|
1586
|
-
workspace canônico. Se um executor explicitamente solicitado não estiver
|
|
1587
|
-
disponível, a execução é bloqueada; não há fallback silencioso.
|
|
1588
|
-
|
|
1589
|
-
Cada executor externo trabalha numa cópia descartável do workspace. Apenas
|
|
1590
|
-
arquivos textuais permitidos pela task card, pertencentes ao agente e dentro do
|
|
1591
|
-
orçamento são promovidos ao armazenamento D5. Remoções, binários, paths fora do
|
|
1592
|
-
plano e violações de propriedade bloqueiam a fase. Claude Code não recebe Bash,
|
|
1593
|
-
WebFetch, WebSearch ou NotebookEdit; os limites de contexto/conclusão do
|
|
1594
|
-
DeepSeek vêm de `src/llm/llm_model_config.py`, e não do arquivo Cordis.
|
|
1595
|
-
|
|
1596
|
-
O executor `pi` roda o loop do harness Pi (`@earendil-works/pi-agent-core` +
|
|
1597
|
-
`pi-ai`) num sidecar Node privado em `pi_harness/worker/`. Ele vem **desligado** em
|
|
1598
|
-
`config/agent-policy.json` (`pi_harness.enabled=false`) e, quando ligado,
|
|
1599
|
-
atende só os owners listados em `pi_harness.agents`. O sidecar não recebe
|
|
1600
|
-
credencial, Git, shell nem o workspace canônico: cada inferência volta para a
|
|
1601
|
-
fachada central em `src/llm/llm_provider.py` e cada ferramenta é revalidada contra
|
|
1602
|
-
o WorkPacket em `pi_harness/tools.py`. Instale o worker com
|
|
1603
|
-
`npm ci --ignore-scripts --prefix pi_harness/worker` e verifique o runtime com
|
|
1604
|
-
`python -m pi_harness.bridge`. Detalhes de operação em
|
|
1605
|
-
[`docs/planos/auditoria/operacao-pi-harness.md`](docs/planos/auditoria/operacao-pi-harness.md).
|
|
1606
|
-
|
|
1607
|
-
Instale os SDKs opcionais em Linux/Docker com
|
|
1608
|
-
`pip install -r requirements-agentic.txt`; a instalação padrão não os exige.
|
|
1609
|
-
As versões testadas são `claude-agent-sdk==0.2.128` e
|
|
1610
|
-
`deepseek-harness-sdk==0.1.1rc2`. O DeepSeek Harness não possui wheel do runtime
|
|
1611
|
-
para Windows e, por isso, seu piloto roda apenas em Linux.
|
|
1612
|
-
|
|
1613
|
-
Exemplo de política (sem credenciais; fallbacks somente em produção):
|
|
129
|
+
Marque o clique que deve iniciar um download com `expect_download: true`. O
|
|
130
|
+
Playwright começa a aguardar o evento antes do clique; após a transferência
|
|
131
|
+
terminar, o MCP verifica o tamanho, sanitiza o nome sugerido e copia o arquivo
|
|
132
|
+
para `JEV_BROWSER_ARTIFACT_DIR` (ou para o diretório de artefatos configurado).
|
|
133
|
+
O resultado inclui a lista `downloaded_files`, com nome, caminho local e bytes;
|
|
134
|
+
cada passo também contém o arquivo capturado. Os limites contam arquivos e bytes
|
|
135
|
+
por fluxo. A checagem de tamanho acontece depois da transferência do navegador,
|
|
136
|
+
antes de copiar para a pasta de artefatos.
|
|
1614
137
|
|
|
1615
138
|
```json
|
|
1616
|
-
{"
|
|
139
|
+
{"action":"click","role":"button","name":"Export report","expect_download":true}
|
|
1617
140
|
```
|
|
1618
141
|
|
|
1619
|
-
|
|
1620
|
-
`
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
guarda somente hashes de evidência e capacidades liberadas.
|
|
1624
|
-
|
|
1625
|
-
O cliente MCP aplica a allowlist antes de cada chamada, protege conteúdo
|
|
1626
|
-
externo contra prompt injection e limita o texto retornado. No PostgreSQL, o
|
|
1627
|
-
modelo não recebe a ferramenta `connect` nem o DSN: o orquestrador conecta com
|
|
1628
|
-
um usuário read-only, entrega somente schema/query/EXPLAIN e desconecta. A
|
|
1629
|
-
validação de migrations usa separadamente `MCP_POSTGRES_VALIDATION_DSN`, que é
|
|
1630
|
-
aceito apenas para hosts allowlisted e bancos efêmeros. Para inspecionar a
|
|
1631
|
-
conectividade real use `GET /mcp/capabilities?probe=true`.
|
|
142
|
+
`jev_browser_mcp.browser.max_download_files`, `max_download_file_bytes`,
|
|
143
|
+
`max_download_total_bytes` e `max_download_timeout_seconds` definem os limites.
|
|
144
|
+
O diretório de artefatos deve ser local e protegido: arquivos baixados podem
|
|
145
|
+
conter dados da aplicação.
|
|
1632
146
|
|
|
1633
|
-
|
|
1634
|
-
proxies publicam exclusivamente a allowlist read-only e reaplicam sanitização e
|
|
1635
|
-
Prompt Guard em cada resultado. Portanto, ferramentas novas ou mutantes que o
|
|
1636
|
-
21st venha a publicar não ficam automaticamente acessíveis ao executor.
|
|
147
|
+
### Auditoria automatizada de acessibilidade
|
|
1637
148
|
|
|
1638
|
-
|
|
149
|
+
Use `audit_accessibility` depois de colocar a página no estado que deseja
|
|
150
|
+
verificar:
|
|
1639
151
|
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
152
|
+
```json
|
|
153
|
+
{"action":"audit_accessibility","standard":"wcag2aa"}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
O MCP usa `@axe-core/playwright` com as tags WCAG 2.0 e 2.1 A/AA. O retorno
|
|
157
|
+
resume as violações por regra, impacto e quantidade de nós, sem copiar HTML ou
|
|
158
|
+
trechos da página. Qualquer violação reprova esse fluxo. A auditoria automática
|
|
159
|
+
encontra problemas comuns, mas não comprova conformidade WCAG completa; combine-a
|
|
160
|
+
com revisão manual e testes com usuários assistivos.
|
|
161
|
+
|
|
162
|
+
Para ações em iframe, acrescente `"frame": "payment-iframe"`; o valor precisa
|
|
163
|
+
corresponder ao atributo `name` ou `title` do iframe. O snapshot inclui o
|
|
164
|
+
conteúdo acessível dos iframes nomeados dentro do escopo, limitado pela
|
|
165
|
+
configuração. Se um alvo estiver ausente, o erro inclui até três nomes acessíveis
|
|
166
|
+
próximos quando o snapshot os encontrar. O plano não aceita JavaScript enviado
|
|
167
|
+
pelo harness, coordenadas ou seletores livres.
|
|
168
|
+
|
|
169
|
+
Os aliases `value` → `text` em `type`/`wait_for_text` e `value` → `expected` em
|
|
170
|
+
asserções são aceitos. `comment` também é aceito em planos e passos, mas é
|
|
171
|
+
ignorado e não é enviado ao Jev. Campos fora do contrato continuam sendo
|
|
172
|
+
recusados.
|
|
173
|
+
|
|
174
|
+
### Upload de arquivos
|
|
175
|
+
|
|
176
|
+
Defina `JEV_BROWSER_UPLOAD_ROOT` como uma pasta absoluta que contenha os arquivos
|
|
177
|
+
de fixture. Cada caminho passado ao fluxo também precisa ser absoluto; o MCP
|
|
178
|
+
resolve links simbólicos e recusa qualquer arquivo fora dessa raiz. Só aceita
|
|
179
|
+
arquivos regulares, até 5 arquivos por passo, 10 MiB por arquivo e 25 MiB no
|
|
180
|
+
total do passo, conforme `jev_browser_mcp.browser` em `config/ui-testing.json`.
|
|
181
|
+
Os bytes são lidos e validados no processo local antes de serem entregues ao
|
|
182
|
+
Playwright. O MCP não envia esses bytes ao Jev nem os inclui diretamente na
|
|
183
|
+
resposta; texto que o próprio site exibir na interface ainda pode aparecer no
|
|
184
|
+
snapshot devolvido ao harness.
|
|
1644
185
|
|
|
1645
|
-
```
|
|
1646
|
-
|
|
186
|
+
```json
|
|
187
|
+
{"action":"upload_file","target":"input","label":"Nota fiscal","file_path":"/fixtures/nota.pdf"}
|
|
1647
188
|
```
|
|
1648
189
|
|
|
1649
|
-
|
|
1650
|
-
|
|
190
|
+
Para uma área de arrastar, use o papel e o nome acessível da dropzone. Para um
|
|
191
|
+
botão que abre a janela nativa, use `target: "button"`; sem `target`, a presença
|
|
192
|
+
de `label` seleciona o input e `role`/`name` seleciona esse botão.
|
|
1651
193
|
|
|
1652
194
|
```json
|
|
1653
|
-
{
|
|
1654
|
-
"mcpServers": {
|
|
1655
|
-
"jev-browser": {
|
|
1656
|
-
"command": "npx",
|
|
1657
|
-
"args": ["--yes", "@diegosouzacdv/jev-browser-mcp"],
|
|
1658
|
-
"env": {
|
|
1659
|
-
"OPENROUTER_API_KEY": "${OPENROUTER_API_KEY}",
|
|
1660
|
-
"JEV_BROWSER_MODE": "harness"
|
|
1661
|
-
}
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
195
|
+
{"action":"upload_file","target":"dropzone","role":"region","name":"Anexos","file_paths":["/fixtures/a.pdf","/fixtures/b.png"]}
|
|
1665
196
|
```
|
|
1666
197
|
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
substituí-los.
|
|
1671
|
-
|
|
1672
|
-
Escolha o navegador por variáveis de ambiente:
|
|
198
|
+
Se `JEV_BROWSER_UPLOAD_ROOT` não estiver definido, a ação recusa a execução. O
|
|
199
|
+
limite evita que um plano transforme o MCP em leitor arbitrário de arquivos do
|
|
200
|
+
computador.
|
|
1673
201
|
|
|
1674
|
-
|
|
1675
|
-
adequado para testes automatizados e CI.
|
|
1676
|
-
- `JEV_BROWSER_MODE=computer` abre o Chrome ou Edge instalado. O MCP usa um
|
|
1677
|
-
perfil persistente próprio; faça login nele uma vez. Não configure o perfil
|
|
1678
|
-
pessoal que já está aberto no computador.
|
|
1679
|
-
- `JEV_BROWSER_CHANNEL=chrome` ou `msedge` escolhe o navegador.
|
|
1680
|
-
- `JEV_BROWSER_PROFILE` define o caminho absoluto do perfil persistente no
|
|
1681
|
-
modo `computer`.
|
|
1682
|
-
|
|
1683
|
-
Para um fluxo conhecido, o agente do harness consulta o cenário e os critérios
|
|
1684
|
-
de aceitação no projeto e chama `run_browser_flow` uma vez. Informe o objetivo,
|
|
1685
|
-
a URL inicial, o resultado esperado e planos candidatos declarativos. O Jev
|
|
1686
|
-
escolhe um dos planos com base no snapshot acessível da página; o MCP executa
|
|
1687
|
-
os passos e confere se o resultado esperado apareceu. Exemplo resumido:
|
|
202
|
+
Exemplo de chamada:
|
|
1688
203
|
|
|
1689
204
|
```json
|
|
1690
205
|
{
|
|
1691
|
-
"flow": "Adicionar o produto ao carrinho",
|
|
206
|
+
"flow": "Adicionar o produto ao carrinho e confirmar o resumo",
|
|
1692
207
|
"initial_url": "http://127.0.0.1:4173/products/coffee",
|
|
1693
208
|
"expected_outcome": "Coffee added to cart",
|
|
209
|
+
"options": {
|
|
210
|
+
"fast_path": true,
|
|
211
|
+
"snapshot_scope": "main",
|
|
212
|
+
"capture_network_errors": true,
|
|
213
|
+
"screenshot_on_failure": true
|
|
214
|
+
},
|
|
1694
215
|
"candidate_plans": {
|
|
1695
|
-
"
|
|
1696
|
-
"description": "Adicionar o produto visível ao carrinho",
|
|
216
|
+
"add_and_confirm": {
|
|
217
|
+
"description": "Adicionar o produto visível ao carrinho e abrir o resumo",
|
|
1697
218
|
"steps": [
|
|
1698
219
|
{"action": "click", "role": "button", "name": "Add to cart"},
|
|
1699
|
-
{"action": "wait_for_text", "text": "Coffee added to cart"}
|
|
220
|
+
{"action": "wait_for_text", "text": "Coffee added to cart"},
|
|
221
|
+
{"action": "click", "role": "link", "name": "View cart"},
|
|
222
|
+
{"action": "assert_text", "role": "heading", "name": "Order summary", "expected": "Coffee"}
|
|
1700
223
|
]
|
|
1701
224
|
}
|
|
1702
225
|
}
|
|
1703
226
|
}
|
|
1704
227
|
```
|
|
1705
228
|
|
|
1706
|
-
O
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
`
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
`
|
|
1758
|
-
`
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
e
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
229
|
+
O plano é montado pelo LLM do harness, mas o Jev escolhe qual plano fornecido
|
|
230
|
+
deve executar usando o fluxo e o snapshot inicial. O Jev não cria ações, nomes
|
|
231
|
+
de controles ou valores de formulário. Valores de `type` são usados localmente
|
|
232
|
+
pelo Playwright e são removidos do texto enviado ao provedor e da evidência de
|
|
233
|
+
retorno. Use valores de teste; autenticação deve ficar no perfil de navegador
|
|
234
|
+
configurado.
|
|
235
|
+
|
|
236
|
+
O MCP também mantém `choose_next_action` para fluxos exploratórios em que o
|
|
237
|
+
harness precisa inspecionar e decidir entre ações uma por vez. Esse caminho é
|
|
238
|
+
mais lento porque exige uma nova decisão e uma nova chamada de ferramenta por
|
|
239
|
+
ação; prefira `run_browser_flow` quando os passos esperados puderem ser
|
|
240
|
+
descritos antes da execução.
|
|
241
|
+
|
|
242
|
+
## Configuração
|
|
243
|
+
|
|
244
|
+
Edite `config/ui-testing.json`. URL, modelo do provedor, variável de credencial
|
|
245
|
+
e limites compartilhados ficam nos blocos `browser` e `jev`. As opções próprias
|
|
246
|
+
do pacote Node ficam no bloco opcional `jev_browser_mcp`, que não altera o
|
|
247
|
+
contrato lido pelo servidor Python. Preserve a estrutura completa exigida pelos
|
|
248
|
+
validadores.
|
|
249
|
+
|
|
250
|
+
`browser.mode` aceita `harness` ou `computer`; o padrão é `computer`:
|
|
251
|
+
|
|
252
|
+
- `harness` usa Chrome headless e contexto isolado, adequado a execuções do
|
|
253
|
+
harness e CI; o estado de autenticação é descartado ao final da chamada.
|
|
254
|
+
- `computer` abre o Chrome ou Edge instalado em modo visível e usa o diretório
|
|
255
|
+
persistente `browser.computer_user_data_dir`, separado por navegador. Não
|
|
256
|
+
reutiliza o perfil pessoal já aberto. Faça login uma vez nesse perfil; os
|
|
257
|
+
cookies permanecem nele entre chamadas.
|
|
258
|
+
|
|
259
|
+
`browser.max_flow_steps` limita a soma de passos declarados entre os planos e
|
|
260
|
+
`browser.max_text_entry_chars` limita cada valor digitado. O resultado contém
|
|
261
|
+
`status`, o plano escolhido, as ações executadas, a última captura acessível e
|
|
262
|
+
se o critério esperado apareceu. Quando existem asserções explícitas, `status`
|
|
263
|
+
também pode ser `passed` com todas elas satisfeitas; `assertions_passed` registra
|
|
264
|
+
esse resultado e `expected_outcome_visible` continua descrevendo somente o
|
|
265
|
+
texto global. `incomplete` significa que nenhum critério foi comprovado;
|
|
266
|
+
confiança do Jev não substitui essa verificação. `timings_ms.ready_ms` mede a
|
|
267
|
+
espera da SPA; `warnings` registra capturas vazias durante transições; e
|
|
268
|
+
`failed_step` identifica índice, ação, alvo, timeout e erro resumido quando uma
|
|
269
|
+
etapa falha.
|
|
270
|
+
|
|
271
|
+
`jev_browser_mcp.browser.max_action_timeout_seconds` limita esperas por ações,
|
|
272
|
+
seletores e condições. `JEV_BROWSER_UPLOAD_ROOT` libera uploads somente dentro
|
|
273
|
+
de uma pasta absoluta escolhida pelo operador.
|
|
274
|
+
`jev_browser_mcp.browser.max_upload_files`, `max_upload_path_chars`,
|
|
275
|
+
`max_upload_file_bytes` e `max_upload_total_bytes` limitam quantidade e tamanho.
|
|
276
|
+
Não configure a raiz como o disco inteiro ou a pasta home: use uma pasta de
|
|
277
|
+
fixtures dedicada.
|
|
278
|
+
|
|
279
|
+
Os limites de download ficam no mesmo bloco: `max_download_files`,
|
|
280
|
+
`max_download_file_bytes`, `max_download_total_bytes` e
|
|
281
|
+
`max_download_timeout_seconds`. `jev_browser_mcp.jev.max_accessibility_violations`
|
|
282
|
+
limita quantas descrições de violações axe entram no resultado; a contagem total
|
|
283
|
+
continua informada mesmo quando a lista é truncada.
|
|
284
|
+
|
|
285
|
+
O `run_browser_flow` aceita `options` com `fast_path`, `snapshot_scope`,
|
|
286
|
+
`block_trackers`, `capture_console_errors`, `capture_network_errors`,
|
|
287
|
+
`capture_network_error_bodies`, `ready_timeout_seconds`, `ready_network_idle`,
|
|
288
|
+
`ready_stable_ms`, `ready_text`, `reuse_page`, `screenshot_on_failure` e
|
|
289
|
+
`trace_on_failure`. Sem override, os padrões são lidos de `jev_browser_mcp` em
|
|
290
|
+
`config/ui-testing.json`: um plano candidato pula a chamada Decisions;
|
|
291
|
+
captura de console e screenshot de falha ficam ligadas; trace, rede e bloqueio
|
|
292
|
+
de recursos ficam desligados. O browser espera a SPA renderizar uma captura
|
|
293
|
+
acessível estável. `ready_text` pode identificar o conteúdo que marca a
|
|
294
|
+
prontidão. `reuse_page: true` pula a navegação somente quando a página e
|
|
295
|
+
`initial_url` têm a mesma origem. `snapshot_scope` aceita `body`, `main` ou
|
|
296
|
+
`dialog`.
|
|
297
|
+
|
|
298
|
+
`block_trackers: true` bloqueia os domínios e tipos de recurso listados na
|
|
299
|
+
configuração (analytics, Hotjar, fontes externas e mídia). Isso pode alterar o
|
|
300
|
+
layout ou o comportamento do site, então a opção é desligada por padrão.
|
|
301
|
+
|
|
302
|
+
Com `capture_console_errors` e `capture_network_errors`, o retorno traz
|
|
303
|
+
`console_errors` e `network_failures`, limitados em quantidade e tamanho.
|
|
304
|
+
Respostas HTTP 4xx/5xx e requisições que falham são listadas; query strings,
|
|
305
|
+
fragmentos, valores de formulário e nomes de arquivo são removidos ou
|
|
306
|
+
sanitizados. `capture_network_error_bodies: true` lê somente respostas JSON
|
|
307
|
+
4xx/5xx da mesma origem, limita o corpo e inclui apenas o campo textual
|
|
308
|
+
`message`, também sanitizado. Em falhas, `screenshot_on_failure` salva
|
|
309
|
+
screenshot local e retorna
|
|
310
|
+
`screenshot_path`. `trace_on_failure: true` também grava um `.zip` compatível
|
|
311
|
+
com o Trace Viewer do Playwright e retorna `trace_path`. O diretório padrão é
|
|
312
|
+
`~/.cache/orquestrador/jev-browser-artifacts`; `JEV_BROWSER_ARTIFACT_DIR` pode
|
|
313
|
+
substituí-lo por uma pasta absoluta fora do pacote. Screenshots e traces podem
|
|
314
|
+
conter dados visíveis da aplicação: mantenha o diretório local protegido e
|
|
315
|
+
compartilhe os arquivos somente se o teste permitir.
|
|
316
|
+
|
|
317
|
+
`harness_browser` e `computer_browser` aceitam `chrome` ou `msedge`.
|
|
318
|
+
`computer_user_data_dir` deve ficar fora do repositório e conter `{browser}`;
|
|
319
|
+
o diretório persistente armazena dados de login e é resolvido sob a pasta home
|
|
320
|
+
do usuário quando começa com `~`. O Playwright MCP fica desabilitado até
|
|
321
|
+
`ORQUESTRADOR_MCP_PLAYWRIGHT_ENABLED=1` ser configurado no ambiente do harness.
|
|
322
|
+
|
|
323
|
+
Defina o valor secreto na variável indicada por `jev.credential_env`, usando o
|
|
324
|
+
secret manager ou ambiente do processo que inicia o harness. Não grave a chave
|
|
325
|
+
em `config/ui-testing.json`. `provider_url` é o endpoint HTTPS completo da API
|
|
326
|
+
Decisions. O cliente não
|
|
327
|
+
segue redirects e recusa URL com credencial, query string ou fragmento. O Jev
|
|
328
|
+
fica indisponível quando a política de MCP está em modo offline.
|
|
329
|
+
|
|
330
|
+
Na primeira execução, aqueça uma vez o cache local do pacote declarado em
|
|
331
|
+
`browser.playwright_mcp_package` com `npx --yes <pacote> --help`. O MCP inicia
|
|
332
|
+
depois com `--offline`, evitando uma consulta ao registry npm em cada fluxo.
|
|
333
|
+
Quando a versão configurada mudar, aqueça o novo pacote uma vez.
|
|
334
|
+
|
|
335
|
+
## Desempenho e evidência
|
|
336
|
+
|
|
337
|
+
O pacote Node usa a decisão remota do Jev para escolher entre múltiplos planos.
|
|
338
|
+
Com exatamente um plano e `fast_path` ligado (padrão), executa esse plano sem
|
|
339
|
+
chamar a API Decisions; `jev_decisions` fica em zero. Inclua o fluxo completo em
|
|
340
|
+
um plano candidato para evitar chamadas separadas ao harness; seleção,
|
|
341
|
+
navegação, ações e asserções ficam em uma chamada MCP. O servidor Python
|
|
342
|
+
`mcp_servers/jev_browser_server.py` mantém seu fluxo próprio e usa uma decisão
|
|
343
|
+
remota do Jev por chamada. A sessão Playwright fecha antes de o MCP Python
|
|
344
|
+
retornar. O perfil do modo `computer` preserva o login para chamadas seguintes;
|
|
345
|
+
nesse servidor o processo e a janela não são reutilizados. O pacote Node mantém
|
|
346
|
+
o browser aquecido até o harness encerrar o processo. O snapshot enviado ao Jev
|
|
347
|
+
e devolvido ao harness remove o rodapé e, se ainda exceder o limite, mantém o
|
|
348
|
+
início e o fim da captura com um marcador de truncamento. `snapshot_scope` pode
|
|
349
|
+
limitar a captura a `main` ou `dialog`; iframes nomeados contidos nesse escopo
|
|
350
|
+
também podem ser incluídos.
|
|
351
|
+
|
|
352
|
+
A chamada composta aquecida deve ficar dentro da meta de 20 segundos em páginas
|
|
353
|
+
que respondem normalmente; a inicialização fria, páginas lentas, MFA e conteúdo
|
|
354
|
+
sob demanda podem excedê-la. O MCP devolve `total_ms`, `browser_session_ms`,
|
|
355
|
+
`navigation_ms`, `initial_snapshot_ms`, `jev_decision_ms` e `browser_plan_ms`
|
|
356
|
+
para localizar o custo. O teto observado em um fluxo sintético local anterior
|
|
357
|
+
foi 6,411 s; isso não mede o Instagram nem garante o mesmo tempo em outros sites.
|
|
358
|
+
|
|
359
|
+
O snapshot inicial, o fluxo, o resultado esperado e as descrições dos planos
|
|
360
|
+
são enviados ao endpoint Decisions quando há mais de uma opção. Com fast-path,
|
|
361
|
+
um plano não gera chamada remota. Os passos, valores digitados, valores
|
|
362
|
+
esperados pelas asserções, caminhos e conteúdo dos arquivos não são enviados.
|
|
363
|
+
Não inclua segredos em `flow`, `expected_outcome` ou nas descrições. A resposta
|
|
364
|
+
retorna o plano escolhido quando houver decisão, custo/confiança do provedor
|
|
365
|
+
quando disponíveis e o snapshot final sanitizado. O fluxo passa quando o texto
|
|
366
|
+
esperado aparece no snapshot ou quando todas as asserções declaradas passam.
|
|
367
|
+
|
|
368
|
+
`network_idle` é uma espera limitada e opcional; páginas com polling ou conexões
|
|
369
|
+
contínuas podem atingir o timeout. Prefira `wait_for_text` e asserções de
|
|
370
|
+
elemento quando houver um sinal de interface específico.
|
|
371
|
+
|
|
372
|
+
Consulte a [introdução do Jev](https://docs.typesafe.ai/introduction) e o
|
|
373
|
+
[tutorial da API Decisions no OpenRouter](https://openrouter.ai/docs/guides/community/jev-tutorial)
|
|
374
|
+
para os tipos de resposta e autenticação.
|