hobots 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. hobots-1.0.0/.gitignore +245 -0
  2. hobots-1.0.0/LICENSE +21 -0
  3. hobots-1.0.0/PKG-INFO +405 -0
  4. hobots-1.0.0/README.md +356 -0
  5. hobots-1.0.0/docs/README.md +32 -0
  6. hobots-1.0.0/docs/api-reference.md +309 -0
  7. hobots-1.0.0/docs/concepts.md +202 -0
  8. hobots-1.0.0/docs/getting-started.md +136 -0
  9. hobots-1.0.0/docs/protocol.md +189 -0
  10. hobots-1.0.0/docs/tasks.md +241 -0
  11. hobots-1.0.0/docs/telemetry.md +221 -0
  12. hobots-1.0.0/examples/README.md +62 -0
  13. hobots-1.0.0/examples/config.py +21 -0
  14. hobots-1.0.0/examples/full.py +80 -0
  15. hobots-1.0.0/examples/tasks/01_basic.py +61 -0
  16. hobots-1.0.0/examples/tasks/02_logs_cancel.py +73 -0
  17. hobots-1.0.0/examples/tasks/03_concurrency.py +62 -0
  18. hobots-1.0.0/examples/tasks/04_idempotency.py +80 -0
  19. hobots-1.0.0/examples/tasks/05_shutdown.py +86 -0
  20. hobots-1.0.0/examples/tasks/06_attachments.py +104 -0
  21. hobots-1.0.0/examples/telemetry/01_errors.py +84 -0
  22. hobots-1.0.0/examples/telemetry/02_scope.py +80 -0
  23. hobots-1.0.0/examples/telemetry/03_transactions.py +80 -0
  24. hobots-1.0.0/examples/telemetry/04_heartbeat.py +46 -0
  25. hobots-1.0.0/examples/telemetry/05_advanced.py +65 -0
  26. hobots-1.0.0/pyproject.toml +83 -0
  27. hobots-1.0.0/src/hobots/__about__.py +3 -0
  28. hobots-1.0.0/src/hobots/__init__.py +159 -0
  29. hobots-1.0.0/src/hobots/_client.py +206 -0
  30. hobots-1.0.0/src/hobots/_config.py +126 -0
  31. hobots-1.0.0/src/hobots/_core.py +405 -0
  32. hobots-1.0.0/src/hobots/_errors.py +48 -0
  33. hobots-1.0.0/src/hobots/_eventbuilder.py +86 -0
  34. hobots-1.0.0/src/hobots/_heartbeat.py +61 -0
  35. hobots-1.0.0/src/hobots/_http.py +195 -0
  36. hobots-1.0.0/src/hobots/_instrument.py +134 -0
  37. hobots-1.0.0/src/hobots/_scope.py +90 -0
  38. hobots-1.0.0/src/hobots/_scrub.py +28 -0
  39. hobots-1.0.0/src/hobots/_stacktrace.py +78 -0
  40. hobots-1.0.0/src/hobots/_transaction.py +282 -0
  41. hobots-1.0.0/src/hobots/_transport.py +208 -0
  42. hobots-1.0.0/src/hobots/_types.py +137 -0
  43. hobots-1.0.0/src/hobots/_utils.py +149 -0
  44. hobots-1.0.0/src/hobots/protocol.py +235 -0
  45. hobots-1.0.0/src/hobots/py.typed +0 -0
  46. hobots-1.0.0/src/hobots/tasks/__init__.py +1 -0
  47. hobots-1.0.0/src/hobots/tasks/_agent.py +444 -0
  48. hobots-1.0.0/src/hobots/tasks/_api.py +208 -0
  49. hobots-1.0.0/src/hobots/tasks/_attachments.py +98 -0
  50. hobots-1.0.0/src/hobots/tasks/_context.py +659 -0
  51. hobots-1.0.0/src/hobots/tasks/_run_context.py +49 -0
  52. hobots-1.0.0/src/hobots/tasks/_shutdown.py +93 -0
  53. hobots-1.0.0/src/hobots/tasks/_types.py +15 -0
  54. hobots-1.0.0/src/hobots/tasks/protocol.py +188 -0
  55. hobots-1.0.0/tests/conftest.py +224 -0
  56. hobots-1.0.0/tests/test_agent.py +353 -0
  57. hobots-1.0.0/tests/test_attachments.py +375 -0
  58. hobots-1.0.0/tests/test_client.py +309 -0
  59. hobots-1.0.0/tests/test_config.py +106 -0
  60. hobots-1.0.0/tests/test_protocol.py +44 -0
  61. hobots-1.0.0/tests/test_scrub.py +33 -0
  62. hobots-1.0.0/tests/test_stacktrace.py +143 -0
  63. hobots-1.0.0/tests/test_transaction.py +248 -0
  64. hobots-1.0.0/tests/test_transport.py +175 -0
@@ -0,0 +1,245 @@
1
+
2
+ # Created by https://www.gitignore.io/api/node,macos,intellij,sublimetext,visualstudiocode
3
+
4
+ ### Intellij ###
5
+ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
6
+ # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
7
+
8
+ # Elastic Beanstalk Files
9
+ .elasticbeanstalk/*
10
+ !.elasticbeanstalk/*.cfg.yml
11
+ !.elasticbeanstalk/*.global.yml
12
+ .serverless
13
+
14
+ **/*.pdf
15
+
16
+ .turbo
17
+ .turbo/**
18
+
19
+ .next
20
+ .next/**
21
+
22
+ dist
23
+ dist/**
24
+
25
+ uploads
26
+
27
+ env.json
28
+ .env.*
29
+ !.env.example
30
+
31
+ .lock
32
+ yarn.lock
33
+ package-lock.json
34
+
35
+ # User-specific stuff:
36
+ .idea/**/workspace.xml
37
+ .idea/**/tasks.xml
38
+ .idea/dictionaries
39
+ ./scripts/seed.sql
40
+
41
+ # Sensitive or high-churn files:
42
+ .idea/**/dataSources/
43
+ .idea/**/dataSources.ids
44
+ .idea/**/dataSources.xml
45
+ .idea/**/dataSources.local.xml
46
+ .idea/**/sqlDataSources.xml
47
+ .idea/**/dynamic.xml
48
+ .idea/**/uiDesigner.xml
49
+
50
+ # Gradle:
51
+ .idea/**/gradle.xml
52
+ .idea/**/libraries
53
+
54
+ # CMake
55
+ cmake-build-debug/
56
+
57
+ # Mongo Explorer plugin:
58
+ .idea/**/mongoSettings.xml
59
+
60
+ ## File-based project format:
61
+ *.iws
62
+
63
+ ## Plugin-specific files:
64
+
65
+ # IntelliJ
66
+ /out/
67
+
68
+ # mpeltonen/sbt-idea plugin
69
+ .idea_modules/
70
+
71
+ # JIRA plugin
72
+ atlassian-ide-plugin.xml
73
+
74
+ # Cursive Clojure plugin
75
+ .idea/replstate.xml
76
+
77
+ # Ruby plugin and RubyMine
78
+ /.rakeTasks
79
+
80
+ # Crashlytics plugin (for Android Studio and IntelliJ)
81
+ com_crashlytics_export_strings.xml
82
+ crashlytics.properties
83
+ crashlytics-build.properties
84
+ fabric.properties
85
+
86
+ ### Intellij Patch ###
87
+ # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
88
+
89
+ # *.iml
90
+ # modules.xml
91
+ # .idea/misc.xml
92
+ # *.ipr
93
+
94
+ # Sonarlint plugin
95
+ .idea/sonarlint
96
+
97
+ ### macOS ###
98
+ *.DS_Store
99
+ .AppleDouble
100
+ .LSOverride
101
+
102
+ # Icon must end with two \r
103
+ Icon
104
+
105
+ # Thumbnails
106
+ ._*
107
+
108
+ # Files that might appear in the root of a volume
109
+ .DocumentRevisions-V100
110
+ .fseventsd
111
+ .Spotlight-V100
112
+ .TemporaryItems
113
+ .Trashes
114
+ .VolumeIcon.icns
115
+ .com.apple.timemachine.donotpresent
116
+
117
+ # Directories potentially created on remote AFP share
118
+ .AppleDB
119
+ .AppleDesktop
120
+ Network Trash Folder
121
+ Temporary Items
122
+ .apdisk
123
+
124
+ ### Node ###
125
+ # Logs
126
+ logs
127
+ *.log
128
+ npm-debug.log*
129
+ yarn-debug.log*
130
+ yarn-error.log*
131
+
132
+ # Runtime data
133
+ pids
134
+ *.pid
135
+ *.seed
136
+ *.pid.lock
137
+
138
+ # Directory for instrumented libs generated by jscoverage/JSCover
139
+ lib-cov
140
+
141
+ # Coverage directory used by tools like istanbul
142
+ coverage
143
+
144
+ # nyc test coverage
145
+ .nyc_output
146
+
147
+ # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
148
+ .grunt
149
+
150
+ # Bower dependency directory (https://bower.io/)
151
+ bower_components
152
+
153
+ # node-waf configuration
154
+ .lock-wscript
155
+
156
+ # Compiled binary addons (http://nodejs.org/api/addons.html)
157
+ build/Release
158
+
159
+ # Dependency directories
160
+ node_modules/
161
+ jspm_packages/
162
+
163
+ # Typescript v1 declaration files
164
+ typings/
165
+
166
+ # Optional npm cache directory
167
+ .npm
168
+
169
+ # Optional eslint cache
170
+ .eslintcache
171
+
172
+ # Optional REPL history
173
+ .node_repl_history
174
+
175
+ # Output of 'npm pack'
176
+ *.tgz
177
+
178
+ # Yarn Integrity file
179
+ .yarn-integrity
180
+
181
+ # dotenv environment variables file
182
+ .env
183
+ .env.test
184
+
185
+ ### SublimeText ###
186
+ # cache files for sublime text
187
+ *.tmlanguage.cache
188
+ *.tmPreferences.cache
189
+ *.stTheme.cache
190
+
191
+ # workspace files are user-specific
192
+ *.sublime-workspace
193
+
194
+ # project files should be checked into the repository, unless a significant
195
+ # proportion of contributors will probably not be using SublimeText
196
+ # *.sublime-project
197
+
198
+ # sftp configuration file
199
+ sftp-config.json
200
+
201
+ # Package control specific files
202
+ Package Control.last-run
203
+ Package Control.ca-list
204
+ Package Control.ca-bundle
205
+ Package Control.system-ca-bundle
206
+ Package Control.cache/
207
+ Package Control.ca-certs/
208
+ Package Control.merged-ca-bundle
209
+ Package Control.user-ca-bundle
210
+ oscrypto-ca-bundle.crt
211
+ bh_unicode_properties.cache
212
+
213
+ # Sublime-github package stores a github token in this file
214
+ # https://packagecontrol.io/packages/sublime-github
215
+ GitHub.sublime-settings
216
+
217
+ ### VisualStudioCode ###
218
+ .vscode/*
219
+ !.vscode/settings.json
220
+ !.vscode/tasks.json
221
+ !.vscode/launch.json
222
+ !.vscode/extensions.json
223
+ .history
224
+
225
+
226
+ # End of https://www.gitignore.io/api/node,macos,intellij,sublimetext,visualstudiocode
227
+
228
+ ### Python (packages/hobots-sdk-python) ###
229
+ __pycache__/
230
+ *.py[cod]
231
+ *$py.class
232
+ *.egg-info/
233
+ .eggs/
234
+ build/
235
+ .venv/
236
+ venv/
237
+ ENV/
238
+ .pytest_cache/
239
+ .mypy_cache/
240
+ .ruff_cache/
241
+ .tox/
242
+ .coverage
243
+ .coverage.*
244
+ htmlcov/
245
+ # `dist` (usado pelo build do Python também) já é ignorado acima
hobots-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hobots
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
hobots-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,405 @@
1
+ Metadata-Version: 2.4
2
+ Name: hobots
3
+ Version: 1.0.0
4
+ Summary: SDK Python da Hobots — telemetria (erros, transactions e heartbeats) e execução remota de tarefas sob demanda (tasks) para RPAs e integrações. Um único import e um único init().
5
+ Project-URL: Homepage, https://hobots.app
6
+ Author: Hobots
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026 Hobots
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ License-File: LICENSE
29
+ Keywords: automation,hobots,monitoring,rpa,tasks,telemetry
30
+ Classifier: Development Status :: 5 - Production/Stable
31
+ Classifier: Intended Audience :: Developers
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Operating System :: OS Independent
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Programming Language :: Python :: 3.9
36
+ Classifier: Programming Language :: Python :: 3.10
37
+ Classifier: Programming Language :: Python :: 3.11
38
+ Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: Programming Language :: Python :: 3.13
40
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
41
+ Classifier: Topic :: System :: Monitoring
42
+ Classifier: Typing :: Typed
43
+ Requires-Python: >=3.9
44
+ Provides-Extra: dev
45
+ Requires-Dist: mypy>=1.10; extra == 'dev'
46
+ Requires-Dist: pytest>=8.0; extra == 'dev'
47
+ Requires-Dist: ruff>=0.6; extra == 'dev'
48
+ Description-Content-Type: text/markdown
49
+
50
+ # hobots
51
+
52
+ SDK Python da **Hobots** para instrumentar agentes (RPAs) e integrações.
53
+
54
+ - **Telemetria** — o que o programa **emite** sozinho: erros com stack trace (módulo **Issues** do app), performance (transactions/etapas) e heartbeats de uptime (módulo **Agentes**).
55
+ - **Tasks** — execução **sob demanda**: o agente faz polling no app da Hobots, executa handlers registrados e reporta status e logs (módulo **Solicitações**).
56
+
57
+ **Um único import e um único `init()`** cobrem as duas capabilities. Cliente HTTP puro, **sem dependências de runtime** (só a stdlib). Requer **Python 3.9+**.
58
+
59
+ A API é **síncrona**: handlers são funções normais (`def`), e o SDK cuida das threads de fundo. É o que combina com Selenium, Playwright síncrono, pyautogui e código legado.
60
+
61
+ > **Todas as durações são em milissegundos**, como no SDK Node: `poll_interval=5000` são 5 segundos, `ctx.sleep(2000)` dorme 2 segundos.
62
+
63
+ ---
64
+
65
+ ## Instalação
66
+
67
+ ```bash
68
+ pip install hobots
69
+ ```
70
+
71
+ ```python
72
+ import hobots # um import só — telemetria e tasks
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Um SDK, duas capabilities, um `init()`
78
+
79
+ As duas capabilities são autenticadas pelo **mesmo Client Secret do agente** e configuradas em um único `init()`:
80
+
81
+ | Capability | Como habilita | Para quê | Ciclo de vida |
82
+ | --- | --- | --- | --- |
83
+ | **Telemetria** | sempre ativa no `init()` | O que o programa emite sozinho: erros, transactions/etapas, heartbeat. | Threads **daemon** — **não** seguram o processo. Chame `close()` no fim. |
84
+ | **Tasks** | `tasks=True` (ou opções) no `init()` | Execução remota sob demanda via polling. | `start()` **segura o processo vivo** até `stop()`/`close()`. |
85
+
86
+ Você usa **uma, outra ou as duas**:
87
+
88
+ - Só saber se o robô quebrou, quanto demora e se está vivo? → `init(client_secret=..., instance_id=..., heartbeat=True)`.
89
+ - Disparar o robô sob demanda a partir de um formulário no app? → `init(..., tasks=True)` + handlers + `run_forever()`.
90
+ - Ambos? Passe `heartbeat` e `tasks` no mesmo `init()` — o `instance_id` é a identidade única, e os dois canais convergem em **um agente** no painel. Com `tasks=True`, o heartbeat é **opcional**: o próprio poll de tasks já marca a presença do agente.
91
+
92
+ ```python
93
+ import hobots
94
+
95
+ hobots.init(
96
+ client_secret="hb_<64 hex>",
97
+ instance_id="vm-cliente-01",
98
+ heartbeat=True, # telemetria: "estou vivo" a cada 30s
99
+ tasks={"poll_interval": 5_000}, # execução sob demanda
100
+ )
101
+
102
+
103
+ @hobots.task("processar-nfe")
104
+ def processar_nfe(params, ctx):
105
+ ctx.log.info(f"processando o lote {params['lote']}…")
106
+ ctx.log.success("12 notas processadas") # o retorno é ignorado — reporte por log
107
+
108
+
109
+ hobots.run_forever() # inicia o polling e bloqueia até Ctrl-C/SIGTERM
110
+ ```
111
+
112
+ ---
113
+
114
+ ## O Client Secret
115
+
116
+ Cada agente tem **um único Client Secret** — a mesma credencial autentica a telemetria **e** as tasks:
117
+
118
+ ```
119
+ hb_<64 hex>
120
+ ```
121
+
122
+ A credencial é **secreta** e viaja sempre no header `Authorization: Bearer` — nunca na URL, e nunca no upload de anexo para o S3. Copie o Client Secret no app, em **Configurações → Agentes**.
123
+
124
+ O SDK fala com a API oficial `https://api2.hobots.app` automaticamente; em desenvolvimento, aponte para outro host com a env `HOBOTS_API_URL`:
125
+
126
+ ```bash
127
+ HOBOTS_API_URL=http://localhost:3001 python meu_robo.py
128
+ ```
129
+
130
+ O secret nunca aparece em `repr()` nem em serialização do `Client`, do `InitOptions` ou do `ResolvedConfig` — só no acesso explícito (`get_client().config.key`).
131
+
132
+ ---
133
+
134
+ ## Telemetria
135
+
136
+ Todas as funções abaixo são **no-op seguras** se `hobots.init()` não foi chamado.
137
+
138
+ ### `init(**options)`
139
+
140
+ Inicialize **uma vez**, o mais cedo possível no processo. Cria o cliente, instala handlers globais de erro (a menos que `capture_unhandled=False`) e inicia o heartbeat se configurado.
141
+
142
+ ```python
143
+ hobots.init(
144
+ client_secret="hb_<64 hex>",
145
+ instance_id="vm-cliente-01",
146
+ environment="production",
147
+ release="robo-nfe@1.4.2",
148
+ tags={"squad": "automacoes"},
149
+ heartbeat=True,
150
+ )
151
+ ```
152
+
153
+ | Opção | Tipo | Default | Descrição |
154
+ | --- | --- | --- | --- |
155
+ | `client_secret` | `str` | **obrigatório** | o Client Secret do agente (`hb_<64 hex>`) |
156
+ | `instance_id` | `str` | **obrigatório** | identidade única da instância — heartbeat e poll de tasks convergem nela |
157
+ | `environment` | `str` | `'production'` | ambiente lógico |
158
+ | `release` | `str` | — | versão do programa, ex. `robo-nfe@1.4.2` |
159
+ | `tags` | `dict[str, str]` | — | tags aplicadas a todos os eventos |
160
+ | `sample_rate` | `float` | `1.0` | amostragem de erros (0..1) |
161
+ | `traces_sample_rate` | `float` | `1.0` | amostragem de transactions (0..1) |
162
+ | `max_breadcrumbs` | `int` | `100` | tamanho do buffer de breadcrumbs |
163
+ | `capture_unhandled` | `bool` | `True` | handlers globais de erro (`sys.excepthook`, `threading.excepthook`, `atexit`) |
164
+ | `heartbeat` | `bool \| HeartbeatOptions \| dict` | `False` | heartbeat periódico |
165
+ | `tasks` | `bool \| TasksOptions \| dict` | `False` | habilita a execução sob demanda |
166
+ | `before_send` | `(event) -> event \| None` | — | edita/descarta evento (retorne `None` para descartar) |
167
+ | `debug` | `bool` | `False` | loga atividade do SDK |
168
+ | `flush_interval` | `int` (ms) | `2000` | intervalo do flush automático do transport |
169
+ | `max_batch_size` | `int` | `10` | itens por envelope antes do flush imediato |
170
+
171
+ ### Captura de erros e mensagens
172
+
173
+ ```python
174
+ try:
175
+ processar_nota(nota)
176
+ except Exception as error:
177
+ hobots.capture_exception(
178
+ error,
179
+ {
180
+ "level": "error",
181
+ "tags": {"cliente": "acme"},
182
+ "extra": {"numero": nota.numero},
183
+ "fingerprint": ["sefaz-timeout"], # agrupa manualmente
184
+ },
185
+ )
186
+ raise
187
+
188
+ hobots.capture_message("Lote processado com sucesso", "success")
189
+ ```
190
+
191
+ As duas devolvem o `event_id` (ou `None` se o SDK não foi inicializado). O hint aceita `level`, `tags`, `extra` e `fingerprint`.
192
+
193
+ Erros **não tratados** são capturados e flushados automaticamente antes do processo sair, preservando o comportamento padrão do Python (traceback + exit 1). Exceções em threads também são capturadas (`threading.excepthook`). `KeyboardInterrupt` e `SystemExit` **não** são capturados — são intenção do operador, não defeito. Desligue tudo com `capture_unhandled=False`.
194
+
195
+ `Severity = 'fatal' | 'error' | 'warning' | 'info' | 'debug' | 'success'`. Use `'success'` para reportar itens concluídos com êxito — contam como eventos, mas **não** viram issue nem disparam alertas.
196
+
197
+ ### Breadcrumbs
198
+
199
+ A trilha do que aconteceu **antes** do erro. Não são enviados sozinhos: viajam anexados ao próximo evento.
200
+
201
+ ```python
202
+ hobots.add_breadcrumb(message="abriu o portal", category="nav")
203
+ hobots.add_breadcrumb({"message": "login efetuado", "level": "info"})
204
+ ```
205
+
206
+ ### Scope
207
+
208
+ Dados que acompanham os próximos eventos:
209
+
210
+ ```python
211
+ hobots.set_tag("cliente", "acme")
212
+ hobots.set_tags({"regiao": "sudeste", "turno": "noite"})
213
+ hobots.set_user({"id": "42", "username": "operador"})
214
+ hobots.set_context("portal", {"url": "https://portal.exemplo"})
215
+ hobots.set_extra("tentativas", 3)
216
+ ```
217
+
218
+ Precedência de tags: `init.tags` → scope → evento → `hint`. `set_context(nome, None)` apaga o grupo; `set_user(None)` remove o usuário.
219
+
220
+ Para isolar alterações num bloco, use o context manager:
221
+
222
+ ```python
223
+ with hobots.with_scope() as scope:
224
+ scope.set_tag("lote", "2026-08")
225
+ hobots.capture_message("processando")
226
+ # a tag `lote` não existe mais aqui
227
+ ```
228
+
229
+ O isolamento é por **thread** (`contextvars`): um `with_scope()` numa thread não afeta as outras.
230
+
231
+ ### Transactions (performance)
232
+
233
+ ```python
234
+ with hobots.start_transaction("sincronizar-notas", "job") as tx:
235
+ with tx.start_child("browser", "login no portal") as login:
236
+ login.set_data("usuario", "operador")
237
+ with tx.start_child("http", "GET /notas") as download:
238
+ download.set_data("quantidade", 12)
239
+ ```
240
+
241
+ Também funciona na forma explícita, com `.finish()`:
242
+
243
+ ```python
244
+ tx = hobots.start_transaction("sincronizar-notas", "job")
245
+ step = tx.start_child("http", "GET /notas")
246
+ step.finish()
247
+ tx.finish() # nada é enviado antes daqui
248
+ ```
249
+
250
+ Steps aninham em profundidade arbitrária (`step.start_child(...)`) e o `finish()` da transaction fecha as que ficaram abertas. `StepStatus = 'ok' | 'warning' | 'error' | 'cancelled'`.
251
+
252
+ ### Heartbeat
253
+
254
+ ```python
255
+ hobots.init(
256
+ client_secret="hb_<64 hex>",
257
+ instance_id="vm-cliente-01",
258
+ heartbeat={"interval": 30_000, "name": "VM 01", "metadata": {"regiao": "sudeste"}},
259
+ )
260
+ ```
261
+
262
+ Bate **imediatamente** no `init()` e depois a cada `interval` ms. O valor vai no payload e define quanto silêncio o servidor tolera antes de marcar a instância offline.
263
+
264
+ ### Encerramento
265
+
266
+ ```python
267
+ hobots.flush(5_000) # empurra a fila; o SDK continua utilizável
268
+ hobots.close(5_000) # encerra tudo: tasks → heartbeat → flush final
269
+ ```
270
+
271
+ As threads da telemetria são **daemon** e não seguram o processo — sempre chame `close()` antes de sair, ou você perde o que estava em buffer.
272
+
273
+ ---
274
+
275
+ ## Tasks (execução sob demanda)
276
+
277
+ Habilite com `tasks=True` (ou opções) no `init()`, registre handlers e chame `run_forever()`.
278
+
279
+ ```python
280
+ hobots.init(
281
+ client_secret="hb_<64 hex>",
282
+ instance_id="vm-cliente-01",
283
+ tasks={"poll_interval": 5_000, "concurrency": 2},
284
+ )
285
+
286
+
287
+ @hobots.task("processar-nfe") # o task_slug do formulário no app
288
+ def processar_nfe(params, ctx):
289
+ ctx.log.info(f"lote {params['lote']}")
290
+
291
+
292
+ hobots.run_forever()
293
+ ```
294
+
295
+ O decorator é equivalente a `hobots.register("processar-nfe", processar_nfe)`.
296
+
297
+ | Opção de `tasks` | Tipo | Default | Descrição |
298
+ | --- | --- | --- | --- |
299
+ | `poll_interval` | `int` (ms) | `5000` (mín. `1000`) | intervalo entre polls **sem trabalho**; com trabalho na fila o próximo poll é imediato |
300
+ | `concurrency` | `int` | `1` | runs simultâneas (clamp 1..5), cada uma na sua thread |
301
+ | `task_timeout` | `int` (ms) | sem timeout | teto client-side por run — **não mata** o handler |
302
+ | `log_flush_interval` | `int` (ms) | `2000` | intervalo do envio incremental de logs (ou na hora, ao acumular 20) |
303
+ | `cancel_check_interval` | `int` (ms) | `5000` (clamp `1000`..`15000`) | detecta cancelamento **e** mantém o sinal de vida da run |
304
+ | `install_exit_handlers` | `bool` | `True` | handlers de SIGINT/SIGTERM que reportam as runs ativas como `AgentTerminated` |
305
+
306
+ ### O `ctx` (RunContext)
307
+
308
+ ```python
309
+ ctx.run_id # id da execução
310
+ ctx.task # nome da task
311
+ ctx.log # .debug/.info/.warn/.error/.success(mensagem)
312
+ ctx.tx # a transaction desta run — NÃO chame finish() nela
313
+ ctx.cancel_event # threading.Event que dispara no cancelamento/timeout/shutdown
314
+ ctx.is_cancelled() # -> bool
315
+ ctx.raise_if_cancelled() # levanta RunCanceled
316
+ ctx.sleep(ms) # espera cancelável (levanta RunCanceled)
317
+ ctx.attach(imagem, ...) # anexa um print, bloqueando até subir -> AttachResult
318
+ ctx.attach_nowait(imagem, ...) # idem, sem esperar o upload
319
+ ```
320
+
321
+ **O retorno do handler é ignorado** — o protocolo não transporta resultado (dados de negócio podem ser sensíveis e não são coletados). Reporte o que importa com `ctx.log`.
322
+
323
+ ### Cancelamento é cooperativo
324
+
325
+ O SDK não consegue interromper código Python de fora. Ele dispara o `cancel_event`; o handler precisa parar:
326
+
327
+ ```python
328
+ @hobots.task("lote-longo")
329
+ def lote_longo(params, ctx):
330
+ for item in itens:
331
+ ctx.raise_if_cancelled() # ponto de parada de uma linha
332
+ processar(item)
333
+ ctx.sleep(1_000) # espera cancelável, no lugar de time.sleep
334
+ ```
335
+
336
+ > ⚠️ **Nunca** capture o `RunCanceled` para retornar normalmente — uma run que retorna sem erro é reportada como `succeeded`. Se precisar fazer limpeza, capture, limpe e **re-levante**.
337
+
338
+ ### Anexos (screenshots)
339
+
340
+ ```python
341
+ @hobots.task("capturar-tela")
342
+ def capturar(params, ctx):
343
+ ctx.attach(page.screenshot(), caption="portal após o login")
344
+ ```
345
+
346
+ Aceita `bytes`, `bytearray`, `memoryview` ou um caminho de arquivo (`str`/`os.PathLike`). A linha de log entra **antes** de qualquer I/O, então a ordem dos logs fica intacta. `attach()` bloqueia até o upload terminar; `attach_nowait()` volta na hora e o SDK drena os uploads pendentes (até 15s) antes de fechar a run.
347
+
348
+ **Nunca levanta**: toda falha vira `AttachResult(ok=False, reason=...)` e um aviso no log da run. `reason` é um de `'unsupported'`, `'too-large'`, `'quota'`, `'read-failed'`, `'unsupported-format'`, `'upload-failed'`, `'canceled'`.
349
+
350
+ Limites (aplicados no SDK **e** no servidor): PNG/JPEG/WEBP, **5 MB** por imagem, **20** por execução, **4** por linha de log.
351
+
352
+ ### Encerramento
353
+
354
+ ```python
355
+ hobots.run_forever(timeout=15_000) # bloqueia até SIGINT/SIGTERM e encerra
356
+ ```
357
+
358
+ Se precisar da main thread para outra coisa, use `hobots.start()` (volta na hora) e encerre você mesmo:
359
+
360
+ ```python
361
+ hobots.start()
362
+ ...
363
+ hobots.close(15_000) # para as tasks (aguardando as runs) + heartbeat + flush
364
+ hobots.stop(30_000) # para SÓ as tasks; a telemetria continua
365
+ ```
366
+
367
+ > ⚠️ O `timeout` do `close()` é gasto **duas vezes, em sequência** — uma esperando as runs, outra no flush final. `close(15_000)` pode levar até ~30s no pior caso. Dimensione pelo prazo do seu SIGTERM (Kubernetes, systemd).
368
+
369
+ ---
370
+
371
+ ## Segurança
372
+
373
+ - O Client Secret viaja **só** no header `Authorization: Bearer`, nunca na URL, e nunca no PUT do anexo para o S3.
374
+ - Mensagens de log, captions, mensagens de erro e stacks passam por `scrub_secrets()` antes de sair do processo — client secrets `hb_…`, tokens Bearer, chaves `sk-…` e credenciais embutidas em URL são redigidos.
375
+ - O secret nunca aparece em `repr()`/serialização do `Client`, `InitOptions` ou `ResolvedConfig`.
376
+ - **Logs, erros e prints são visíveis no app.** Não coloque segredos ou dados sensíveis neles.
377
+
378
+ ---
379
+
380
+ ## Documentação
381
+
382
+ | Documento | Conteúdo |
383
+ | --- | --- |
384
+ | [Getting started](docs/getting-started.md) | instalação, primeiro robô, encerramento |
385
+ | [Conceitos](docs/concepts.md) | evento, tag, breadcrumb, run, transaction, fila… |
386
+ | [Telemetria](docs/telemetry.md) | erros, scope, performance, heartbeat |
387
+ | [Tasks](docs/tasks.md) | polling, handlers, cancelamento, concorrência, anexos |
388
+ | [Referência de API](docs/api-reference.md) | todas as assinaturas, opções e prazos |
389
+ | [Protocolo & transporte](docs/protocol.md) | endpoints, wire format, retries |
390
+ | [Exemplos](examples/) | 11 scripts executáveis, um por funcionalidade |
391
+
392
+ ## Desenvolvimento
393
+
394
+ ```bash
395
+ pip install -e ".[dev]"
396
+ pytest # 123 testes, sem rede (servidor HTTP local nas fixtures)
397
+ ruff check . && ruff format --check .
398
+ mypy src
399
+ ```
400
+
401
+ Publicação: ajuste `__version__` em `src/hobots/__about__.py`, rode `python -m build` (ou `uv build`) e `twine upload dist/*`.
402
+
403
+ ## Licença
404
+
405
+ MIT