hobots 1.9.0 → 1.9.2
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 +9 -7
- package/dist/index.cjs +64 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -43
- package/dist/index.d.ts +38 -43
- package/dist/index.js +64 -9
- package/dist/index.js.map +1 -1
- package/docs/api-reference.md +14 -20
- package/docs/concepts.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/tasks.md +21 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -264,7 +264,6 @@ interface RunContext {
|
|
|
264
264
|
signal: AbortSignal;
|
|
265
265
|
throwIfCanceled(): void; // lança RunCanceled se o signal já disparou
|
|
266
266
|
sleep(ms: number): Promise<void>; // espera cancelável — rejeita com RunCanceled no abort
|
|
267
|
-
attach(image, options?): Promise<AttachResult>; // anexa um print a uma linha de log
|
|
268
267
|
}
|
|
269
268
|
```
|
|
270
269
|
|
|
@@ -274,7 +273,6 @@ interface RunContext {
|
|
|
274
273
|
- **`ctx.signal`** — `AbortSignal` que dispara quando a run é **cancelada no app**, em **timeout** ou no **shutdown**.
|
|
275
274
|
- **`ctx.throwIfCanceled()`** — o ponto de parada de uma linha; lança `RunCanceled` se o `signal` já disparou.
|
|
276
275
|
- **`ctx.sleep(ms)`** — espera que respeita o `signal` (rejeita com `RunCanceled` em vez de dormir até o fim).
|
|
277
|
-
- **`ctx.attach(image, options?)`** — anexa um print da tela à linha de log (veja [Anexos](#anexos-screenshots) abaixo).
|
|
278
276
|
- **Retorno** — **ignorado**: o SDK não captura nem envia o valor retornado pelo handler (resultados podem conter dados sensíveis e não são coletados).
|
|
279
277
|
- **Erro lançado** — a run é reportada como `failed` (mensagem cap 10k, stack 20k).
|
|
280
278
|
|
|
@@ -282,23 +280,27 @@ interface RunContext {
|
|
|
282
280
|
|
|
283
281
|
### Anexos (screenshots)
|
|
284
282
|
|
|
285
|
-
Texto de log conta o que o robô fez; um print conta **como estava a tela**. `
|
|
283
|
+
Texto de log conta o que o robô fez; um print conta **como estava a tela**. Os métodos de captura (`Hobots.captureException` e `Hobots.captureMessage`) aceitam um `attachment` no hint: dentro de uma execução de task, a imagem vira anexo da linha de log da run que espelha a captura — no app ela aparece como miniatura logo abaixo dessa linha, clicável para ampliar.
|
|
286
284
|
|
|
287
285
|
```js
|
|
288
286
|
Hobots.register('processar-nfe', async (params, ctx) => {
|
|
289
287
|
await entrar();
|
|
290
|
-
|
|
288
|
+
Hobots.captureMessage('portal após o login', 'info', {
|
|
289
|
+
attachment: await page.screenshot(),
|
|
290
|
+
});
|
|
291
291
|
|
|
292
292
|
try {
|
|
293
293
|
await emitir(params);
|
|
294
294
|
} catch (erro) {
|
|
295
|
-
|
|
295
|
+
Hobots.captureException(erro, {
|
|
296
|
+
attachment: { image: await page.screenshot(), caption: 'tela no momento do erro' },
|
|
297
|
+
});
|
|
296
298
|
throw erro;
|
|
297
299
|
}
|
|
298
300
|
});
|
|
299
301
|
```
|
|
300
302
|
|
|
301
|
-
**O SDK não tira o print** — ele recebe a imagem pronta, de onde você quiser (Playwright, Puppeteer, uma lib de captura de desktop, um arquivo em disco).
|
|
303
|
+
**O SDK não tira o print** — ele recebe a imagem pronta, de onde você quiser (Playwright, Puppeteer, uma lib de captura de desktop, um arquivo em disco). O `attachment` aceita bytes (`Uint8Array`/`ArrayBuffer`/`Buffer`), um caminho de arquivo (só em Node) ou um objeto `{ image, caption?, contentType? }`.
|
|
302
304
|
|
|
303
305
|
| Limite | Valor |
|
|
304
306
|
| --- | --- |
|
|
@@ -307,7 +309,7 @@ Hobots.register('processar-nfe', async (params, ctx) => {
|
|
|
307
309
|
| anexos por execução | 20 |
|
|
308
310
|
| anexos por linha de log | 4 |
|
|
309
311
|
|
|
310
|
-
A linha de log é registrada **na hora**; o upload roda em segundo plano e vai direto para o S3 por URL presignada, sem passar pela API
|
|
312
|
+
A linha de log é registrada **na hora**; o upload roda em segundo plano e vai direto para o S3 por URL presignada, sem passar pela API — a captura **nunca bloqueia nem lança** por causa do anexo (falhas viram um aviso no log da run). Fora de uma execução de task não há onde pendurar a imagem: o `attachment` é descartado com um aviso. Detalhes em [Anexos](./docs/tasks.md#anexos-screenshots).
|
|
311
313
|
|
|
312
314
|
> **Dados sensíveis:** o print é enviado **exatamente como está** — o SDK não borra nem recorta nada, e telas de portal costumam conter CPF, nome e valores. Capture só o necessário. Solicitantes só veem os anexos quando o projeto está com "mostrar logs para solicitantes" ligado, o mesmo gate dos logs.
|
|
313
315
|
|
package/dist/index.cjs
CHANGED
|
@@ -161,7 +161,7 @@ function scrubSecrets(text) {
|
|
|
161
161
|
|
|
162
162
|
// src/utils.ts
|
|
163
163
|
var SDK_NAME = "hobots";
|
|
164
|
-
var SDK_VERSION = true ? "1.9.
|
|
164
|
+
var SDK_VERSION = true ? "1.9.1" : "0.0.0-dev";
|
|
165
165
|
function isNode() {
|
|
166
166
|
return typeof process !== "undefined" && typeof process.versions?.node === "string";
|
|
167
167
|
}
|
|
@@ -1172,8 +1172,7 @@ var RunReporter = class {
|
|
|
1172
1172
|
throwIfCanceled: () => {
|
|
1173
1173
|
if (signal.aborted) throw runCanceledError();
|
|
1174
1174
|
},
|
|
1175
|
-
sleep: (ms) => cancelableSleep(ms, signal)
|
|
1176
|
-
attach: (image, options) => this.attach(image, options)
|
|
1175
|
+
sleep: (ms) => cancelableSleep(ms, signal)
|
|
1177
1176
|
};
|
|
1178
1177
|
}
|
|
1179
1178
|
context;
|
|
@@ -1318,7 +1317,8 @@ var RunReporter = class {
|
|
|
1318
1317
|
/**
|
|
1319
1318
|
* Registra a linha de log com o id do anexo (síncrono, na ordem certa) e dispara
|
|
1320
1319
|
* o upload em segundo plano. Nunca lança: todo caminho de erro vira `{ ok: false }`
|
|
1321
|
-
* com um aviso no log da run.
|
|
1320
|
+
* com um aviso no log da run. Chega ao handler pelos capture* do SDK — o hint
|
|
1321
|
+
* `attachment` de Hobots.captureException/captureMessage cai aqui via run-context-store.
|
|
1322
1322
|
*/
|
|
1323
1323
|
async attach(image, options = {}) {
|
|
1324
1324
|
const id = uuid4();
|
|
@@ -1505,6 +1505,9 @@ async function runWithRunContext(store, fn) {
|
|
|
1505
1505
|
function currentRunTransaction() {
|
|
1506
1506
|
return storage?.getStore()?.tx;
|
|
1507
1507
|
}
|
|
1508
|
+
function currentRunAttach() {
|
|
1509
|
+
return storage?.getStore()?.attach;
|
|
1510
|
+
}
|
|
1508
1511
|
|
|
1509
1512
|
// src/tasks/shutdown.ts
|
|
1510
1513
|
var WATCHED_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
@@ -1556,6 +1559,7 @@ var MIN_POLL_INTERVAL_MS = 1e3;
|
|
|
1556
1559
|
var MAX_CONCURRENCY = 5;
|
|
1557
1560
|
var POLL_RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
|
|
1558
1561
|
var AUTH_FAILURE_INTERVAL_MS = 3e4;
|
|
1562
|
+
var DEFAULT_CANCEL_GRACE_MS = 1e4;
|
|
1559
1563
|
function hostname() {
|
|
1560
1564
|
if (!isNode()) return void 0;
|
|
1561
1565
|
return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
|
|
@@ -1574,6 +1578,7 @@ var TaskAgent = class {
|
|
|
1574
1578
|
this.taskTimeoutMs = options.taskTimeoutMs;
|
|
1575
1579
|
this.logFlushIntervalMs = options.logFlushIntervalMs;
|
|
1576
1580
|
this.cancelCheckIntervalMs = options.cancelCheckIntervalMs;
|
|
1581
|
+
this.cancelGraceMs = options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS;
|
|
1577
1582
|
this.logger.debug("agente configurado", { baseUrl: config.tasksBaseUrl });
|
|
1578
1583
|
}
|
|
1579
1584
|
logger;
|
|
@@ -1585,6 +1590,7 @@ var TaskAgent = class {
|
|
|
1585
1590
|
taskTimeoutMs;
|
|
1586
1591
|
logFlushIntervalMs;
|
|
1587
1592
|
cancelCheckIntervalMs;
|
|
1593
|
+
cancelGraceMs;
|
|
1588
1594
|
state = "idle";
|
|
1589
1595
|
timer;
|
|
1590
1596
|
pollAbort;
|
|
@@ -1746,7 +1752,10 @@ var TaskAgent = class {
|
|
|
1746
1752
|
}
|
|
1747
1753
|
this.logger.debug(`executando run ${run.id} (${run.task})`);
|
|
1748
1754
|
try {
|
|
1749
|
-
await runWithRunContext(
|
|
1755
|
+
await runWithRunContext(
|
|
1756
|
+
{ runId: run.id, tx, attach: (image, options) => reporter.attach(image, options) },
|
|
1757
|
+
() => this.invokeWithTimeout(handler, run, reporter)
|
|
1758
|
+
);
|
|
1750
1759
|
await reporter.finalizeAttachments();
|
|
1751
1760
|
tx.setStatus("ok");
|
|
1752
1761
|
tx.finish();
|
|
@@ -1766,14 +1775,26 @@ var TaskAgent = class {
|
|
|
1766
1775
|
}
|
|
1767
1776
|
async invokeWithTimeout(handler, run, reporter) {
|
|
1768
1777
|
const invocation = Promise.resolve().then(() => handler(run.params, reporter.context));
|
|
1769
|
-
|
|
1778
|
+
const races = [];
|
|
1779
|
+
if (this.taskTimeoutMs && this.taskTimeoutMs > 0) {
|
|
1780
|
+
races.push(sleep(this.taskTimeoutMs).then(() => TIMEOUT_SENTINEL));
|
|
1781
|
+
}
|
|
1782
|
+
if (this.cancelGraceMs > 0) {
|
|
1783
|
+
races.push(waitForCancelGrace(reporter.context.signal, this.cancelGraceMs).then(() => CANCEL_SENTINEL));
|
|
1784
|
+
}
|
|
1785
|
+
if (races.length === 0) {
|
|
1770
1786
|
await invocation;
|
|
1771
1787
|
return;
|
|
1772
1788
|
}
|
|
1789
|
+
const outcome = await Promise.race([invocation, ...races]);
|
|
1790
|
+
if (outcome !== TIMEOUT_SENTINEL && outcome !== CANCEL_SENTINEL) return;
|
|
1791
|
+
if (outcome === CANCEL_SENTINEL) {
|
|
1792
|
+
this.logger.warn(
|
|
1793
|
+
`run ${run.id} cancelada \u2014 handler n\xE3o respondeu a ctx.signal em ${this.cancelGraceMs}ms, liberando o slot (o handler pode continuar rodando em segundo plano)`
|
|
1794
|
+
);
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1773
1797
|
const timeoutMs = this.taskTimeoutMs;
|
|
1774
|
-
const timeout = sleep(timeoutMs).then(() => TIMEOUT_SENTINEL);
|
|
1775
|
-
const outcome = await Promise.race([invocation, timeout]);
|
|
1776
|
-
if (outcome !== TIMEOUT_SENTINEL) return;
|
|
1777
1798
|
reporter.abort();
|
|
1778
1799
|
invocation.then(() => this.logger.warn(`run ${run.id} terminou depois do timeout de ${timeoutMs}ms`)).catch(() => this.logger.warn(`run ${run.id} falhou depois do timeout de ${timeoutMs}ms (erro descartado)`));
|
|
1779
1800
|
throw Object.assign(new Error(`timeout ap\xF3s ${timeoutMs}ms (o handler pode continuar em background)`), {
|
|
@@ -1782,6 +1803,16 @@ var TaskAgent = class {
|
|
|
1782
1803
|
}
|
|
1783
1804
|
};
|
|
1784
1805
|
var TIMEOUT_SENTINEL = /* @__PURE__ */ Symbol("task-timeout");
|
|
1806
|
+
var CANCEL_SENTINEL = /* @__PURE__ */ Symbol("task-canceled");
|
|
1807
|
+
function waitForCancelGrace(signal, graceMs) {
|
|
1808
|
+
return new Promise((resolve) => {
|
|
1809
|
+
if (signal.aborted) {
|
|
1810
|
+
setTimeout(resolve, graceMs);
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
signal.addEventListener("abort", () => setTimeout(resolve, graceMs), { once: true });
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1785
1816
|
var MAX_ERROR_MESSAGE_CHARS = 1e4;
|
|
1786
1817
|
var MAX_ERROR_STACK_CHARS = 2e4;
|
|
1787
1818
|
function toRunError(error) {
|
|
@@ -1836,10 +1867,34 @@ function init(options) {
|
|
|
1836
1867
|
function getClient() {
|
|
1837
1868
|
return client;
|
|
1838
1869
|
}
|
|
1870
|
+
function toRunLogLevel(level) {
|
|
1871
|
+
return level === "fatal" ? "error" : level;
|
|
1872
|
+
}
|
|
1873
|
+
function forwardAttachment(attachment, message, level) {
|
|
1874
|
+
const attach = currentRunAttach();
|
|
1875
|
+
if (!attach) {
|
|
1876
|
+
client?.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o de task (register/start)");
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1879
|
+
const withMeta = typeof attachment === "object" && "image" in attachment ? attachment : { image: attachment };
|
|
1880
|
+
void attach(withMeta.image, {
|
|
1881
|
+
message,
|
|
1882
|
+
level: toRunLogLevel(level),
|
|
1883
|
+
caption: withMeta.caption,
|
|
1884
|
+
contentType: withMeta.contentType
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1839
1887
|
function captureException(error, hint) {
|
|
1888
|
+
if (hint?.attachment) {
|
|
1889
|
+
const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
1890
|
+
forwardAttachment(hint.attachment, message, hint.level ?? "error");
|
|
1891
|
+
}
|
|
1840
1892
|
return client?.captureException(error, hint, scope);
|
|
1841
1893
|
}
|
|
1842
1894
|
function captureMessage(message, level = "info", hint) {
|
|
1895
|
+
if (hint?.attachment) {
|
|
1896
|
+
forwardAttachment(hint.attachment, message, hint.level ?? level);
|
|
1897
|
+
}
|
|
1843
1898
|
return client?.captureMessage(message, level, hint, scope);
|
|
1844
1899
|
}
|
|
1845
1900
|
function addBreadcrumb(crumb) {
|