hobots 3.3.0 → 3.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 +4 -4
- package/dist/index.cjs +155 -86
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +74 -36
- package/dist/index.d.ts +74 -36
- package/dist/index.js +155 -86
- package/dist/index.js.map +1 -1
- package/dist/protocol.d.cts +5 -3
- package/dist/protocol.d.ts +5 -3
- package/docs/api-reference.md +7 -6
- package/docs/protocol.md +9 -6
- package/docs/tasks.md +26 -7
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -93,12 +93,12 @@ Os dois podem coexistir no mesmo processo: um monitor sempre ligado + tasks sob
|
|
|
93
93
|
|
|
94
94
|
| Membro | O que faz |
|
|
95
95
|
| --- | --- |
|
|
96
|
-
| `hobots.log.debug/info/warn/error/success(msg, ...rest)` | Log da execução, enviado incrementalmente ao app (e ecoado no terminal). Aceita vários argumentos como `console.log` — `hobots.log.info('nota', numero, 'ok')` — cada um vira texto, unidos por espaço. **Nunca lança** — pode ir em `finally`. |
|
|
97
|
-
| `hobots.items.succeeded(payload?, { id }?)` · `.failed(msg, payload?, …)` · `.occurrence(msg, payload?, …)` · `.add({...})` | **A entrega do robô**: cada item processado, com status e atributos (viram colunas no app — declare-as com a opção `items: { columns: [...] }` do `register`/`start`). Bufferizado, em lotes. |
|
|
96
|
+
| `hobots.log.debug/info/warn/error/success(msg, ...rest)` | Log da execução, enviado incrementalmente ao app (e ecoado no terminal). Aceita vários argumentos como `console.log` — `hobots.log.info('nota', numero, 'ok')` — cada um vira texto, unidos por espaço. Um `hobots.image(...)` como **último** argumento pendura um print na linha. **Nunca lança** — pode ir em `finally`. |
|
|
97
|
+
| `hobots.items.succeeded(payload?, { id, image }?)` · `.failed(msg, payload?, …)` · `.occurrence(msg, payload?, …)` · `.add({...})` | **A entrega do robô**: cada item processado, com status e atributos (viram colunas no app — declare-as com a opção `items: { columns: [...] }` do `register`/`start`). `image` pendura um print no item. Bufferizado, em lotes. |
|
|
98
98
|
| `hobots.items.each(lista, async (el, item) => …, { id, payload }?)` | **Loop com o contrato garantido**: cada elemento vira step + item; exceção no handler = item `failed` com a mensagem e o loop **segue** até o fim; progresso automático. Só cancelamento/timeout interrompem. |
|
|
99
99
|
| `hobots.progress.total(n)` · `.advance(k = 1)` · `.set(atual, total?)` · `.current` · `.snapshot` | Barra, percentual e tempo restante na solicitação. Sem `total()` nada é enviado. |
|
|
100
|
-
| `hobots.transaction.startChild(op, descrição)` | Uma **step** no waterfall de performance da execução. A transaction da execução já existe e é finalizada pelo SDK. |
|
|
101
|
-
| `hobots.
|
|
100
|
+
| `hobots.transaction.startChild(op, descrição)` | Uma **step** no waterfall de performance da execução. A transaction da execução já existe e é finalizada pelo SDK. `step.setStatus(status, msg?, { image })` / `setError(err, msg?, { image })` penduram um print na step. |
|
|
101
|
+
| `hobots.image(bytes \| caminho, { caption?, contentType? })` | Embrulha um **print** (PNG/JPEG/WEBP, até 5 MB, 20 por execução) para ir junto de um log, item ou step. Puro: o upload roda em segundo plano quando o evento é registrado; nunca lança nem bloqueia. |
|
|
102
102
|
| `hobots.captureException(erro, hint?)` | Abre uma **issue** (módulo Issues). `hint.attachment` anexa um print à linha de log correspondente. |
|
|
103
103
|
| `hobots.retry(fn, { retries = 3, delayMs = 1_000, shouldRetry? })` | async-retry ciente do cancelamento da execução. `delayMs` aceita função `(tentativa) => ms`. |
|
|
104
104
|
| `hobots.sleep(ms)` | Espera **cancelável**: rejeita com `RunCanceled` se a execução for cancelada. |
|
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,7 @@ __export(src_exports, {
|
|
|
34
34
|
close: () => close2,
|
|
35
35
|
default: () => src_default,
|
|
36
36
|
hobots: () => hobots,
|
|
37
|
+
image: () => image2,
|
|
37
38
|
register: () => register2,
|
|
38
39
|
retry: () => retry,
|
|
39
40
|
start: () => start2
|
|
@@ -95,7 +96,7 @@ function scrubSecrets(text) {
|
|
|
95
96
|
|
|
96
97
|
// src/utils.ts
|
|
97
98
|
var SDK_NAME = "hobots";
|
|
98
|
-
var SDK_VERSION = true ? "3.
|
|
99
|
+
var SDK_VERSION = true ? "3.4.0" : "0.0.0-dev";
|
|
99
100
|
function isNode() {
|
|
100
101
|
return typeof process !== "undefined" && typeof process.versions?.node === "string";
|
|
101
102
|
}
|
|
@@ -377,6 +378,7 @@ var Transport = class {
|
|
|
377
378
|
try {
|
|
378
379
|
response = await fetchWithRetry(
|
|
379
380
|
() => fetch(this.endpoint, {
|
|
381
|
+
redirect: "error",
|
|
380
382
|
method: "POST",
|
|
381
383
|
headers: { "content-type": "application/json", authorization: `Bearer ${this.key}` },
|
|
382
384
|
body: JSON.stringify(envelope),
|
|
@@ -554,7 +556,6 @@ function envBaseUrl() {
|
|
|
554
556
|
function isLoopbackHost(hostname2) {
|
|
555
557
|
return hostname2 === "localhost" || hostname2.endsWith(".localhost") || hostname2.startsWith("127.") || hostname2 === "[::1]" || hostname2 === "::1" || hostname2 === "0.0.0.0";
|
|
556
558
|
}
|
|
557
|
-
var warnedInsecureBaseUrl = false;
|
|
558
559
|
function validateBaseUrl(raw, prefix) {
|
|
559
560
|
let url;
|
|
560
561
|
try {
|
|
@@ -568,11 +569,8 @@ function validateBaseUrl(raw, prefix) {
|
|
|
568
569
|
if (url.username || url.password) {
|
|
569
570
|
throw new Error(`${prefix} HOBOTS_API_URL n\xE3o pode conter credenciais na URL \u2014 a autentica\xE7\xE3o \xE9 s\xF3 pelo Client Secret`);
|
|
570
571
|
}
|
|
571
|
-
if (url.protocol === "http:" && !isLoopbackHost(url.hostname)
|
|
572
|
-
|
|
573
|
-
console.warn(
|
|
574
|
-
`${prefix} HOBOTS_API_URL usa http para o host remoto "${url.hostname}" \u2014 o Client Secret trafega sem TLS`
|
|
575
|
-
);
|
|
572
|
+
if (url.protocol === "http:" && !isLoopbackHost(url.hostname)) {
|
|
573
|
+
throw new Error(`${prefix} HOBOTS_API_URL usa http para o host remoto "${url.hostname}" \u2014 o Client Secret trafegaria sem TLS; use https`);
|
|
576
574
|
}
|
|
577
575
|
return raw.replace(/\/+$/, "");
|
|
578
576
|
}
|
|
@@ -936,6 +934,7 @@ var TasksApi = class {
|
|
|
936
934
|
let response;
|
|
937
935
|
try {
|
|
938
936
|
response = await fetch(`${this.baseUrl}/poll`, {
|
|
937
|
+
redirect: "error",
|
|
939
938
|
method: "POST",
|
|
940
939
|
headers: this.headers(),
|
|
941
940
|
body: JSON.stringify(request),
|
|
@@ -959,6 +958,7 @@ var TasksApi = class {
|
|
|
959
958
|
let response;
|
|
960
959
|
try {
|
|
961
960
|
response = await fetch(`${this.baseUrl}/runs`, {
|
|
961
|
+
redirect: "error",
|
|
962
962
|
method: "POST",
|
|
963
963
|
headers: this.headers(),
|
|
964
964
|
body: JSON.stringify(request),
|
|
@@ -1009,6 +1009,7 @@ var TasksApi = class {
|
|
|
1009
1009
|
try {
|
|
1010
1010
|
response = await fetchWithRetry(
|
|
1011
1011
|
() => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
|
|
1012
|
+
redirect: "error",
|
|
1012
1013
|
method: "PATCH",
|
|
1013
1014
|
headers: this.headers(),
|
|
1014
1015
|
body: JSON.stringify(update),
|
|
@@ -1030,6 +1031,7 @@ var TasksApi = class {
|
|
|
1030
1031
|
*/
|
|
1031
1032
|
async updateRunOnce(runId, update, timeoutMs = 2e3) {
|
|
1032
1033
|
await fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
|
|
1034
|
+
redirect: "error",
|
|
1033
1035
|
method: "PATCH",
|
|
1034
1036
|
headers: this.headers(),
|
|
1035
1037
|
body: JSON.stringify(update),
|
|
@@ -1046,6 +1048,7 @@ var TasksApi = class {
|
|
|
1046
1048
|
try {
|
|
1047
1049
|
response = await fetchWithRetry(
|
|
1048
1050
|
() => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}/items`, {
|
|
1051
|
+
redirect: "error",
|
|
1049
1052
|
method: "POST",
|
|
1050
1053
|
headers: this.headers(),
|
|
1051
1054
|
body: JSON.stringify(request),
|
|
@@ -1075,6 +1078,7 @@ var TasksApi = class {
|
|
|
1075
1078
|
UPDATE_TIMEOUT_MS,
|
|
1076
1079
|
signal,
|
|
1077
1080
|
(timeoutSignal) => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}/attachments`, {
|
|
1081
|
+
redirect: "error",
|
|
1078
1082
|
method: "POST",
|
|
1079
1083
|
headers: this.headers(),
|
|
1080
1084
|
body: JSON.stringify(request),
|
|
@@ -1107,6 +1111,7 @@ var TasksApi = class {
|
|
|
1107
1111
|
UPLOAD_TIMEOUT_MS,
|
|
1108
1112
|
signal,
|
|
1109
1113
|
(timeoutSignal) => fetch(url, {
|
|
1114
|
+
redirect: "error",
|
|
1110
1115
|
method: "PUT",
|
|
1111
1116
|
headers,
|
|
1112
1117
|
// BodyInit não aceita Uint8Array<ArrayBufferLike> em todos os lib.dom; o buffer é o mesmo
|
|
@@ -1125,6 +1130,7 @@ var TasksApi = class {
|
|
|
1125
1130
|
const response = await fetch(
|
|
1126
1131
|
`${this.baseUrl}/runs/${encodeURIComponent(runId)}/attachments/${encodeURIComponent(attachmentId)}`,
|
|
1127
1132
|
{
|
|
1133
|
+
redirect: "error",
|
|
1128
1134
|
method: "PATCH",
|
|
1129
1135
|
headers: this.headers(),
|
|
1130
1136
|
body: JSON.stringify(request),
|
|
@@ -1207,22 +1213,22 @@ var ItemHandle = class {
|
|
|
1207
1213
|
sink;
|
|
1208
1214
|
reported = false;
|
|
1209
1215
|
status;
|
|
1210
|
-
succeeded(payload) {
|
|
1211
|
-
this.record("succeeded", void 0, payload);
|
|
1216
|
+
succeeded(payload, options) {
|
|
1217
|
+
this.record("succeeded", void 0, payload, options?.image);
|
|
1212
1218
|
}
|
|
1213
|
-
failed(message, payload) {
|
|
1214
|
-
this.record("failed", message, payload);
|
|
1219
|
+
failed(message, payload, options) {
|
|
1220
|
+
this.record("failed", message, payload, options?.image);
|
|
1215
1221
|
if (this.step && this.step.status !== "error") this.step.setStatus("error", message);
|
|
1216
1222
|
}
|
|
1217
|
-
occurrence(message, payload) {
|
|
1218
|
-
this.record("occurrence", message, payload);
|
|
1223
|
+
occurrence(message, payload, options) {
|
|
1224
|
+
this.record("occurrence", message, payload, options?.image);
|
|
1219
1225
|
if (this.step && this.step.status === "ok") this.step.setStatus("warning", message);
|
|
1220
1226
|
}
|
|
1221
|
-
record(status, message, payload) {
|
|
1227
|
+
record(status, message, payload, image3) {
|
|
1222
1228
|
this.reported = true;
|
|
1223
1229
|
this.status = status;
|
|
1224
1230
|
const merged = { ...this.basePayload ?? {}, ...payload ?? {} };
|
|
1225
|
-
this.sink.report(status, message, Object.keys(merged).length > 0 ? merged : void 0, this.id);
|
|
1231
|
+
this.sink.report(status, message, Object.keys(merged).length > 0 ? merged : void 0, this.id, image3);
|
|
1226
1232
|
}
|
|
1227
1233
|
};
|
|
1228
1234
|
function resolveId(options, element, index) {
|
|
@@ -1302,6 +1308,27 @@ var MAX_ATTACHMENTS_PER_LOG_ENTRY = WIRE_LIMITS.MAX_ATTACHMENTS_PER_LOG_ENTRY;
|
|
|
1302
1308
|
var MAX_CONCURRENT_UPLOADS = 3;
|
|
1303
1309
|
var MAX_CAPTION_CHARS = WIRE_LIMITS.MAX_CAPTION_CHARS;
|
|
1304
1310
|
var ATTACHMENT_DRAIN_MS = 15e3;
|
|
1311
|
+
var IMAGE_BRAND = /* @__PURE__ */ Symbol.for("hobots.image");
|
|
1312
|
+
var ImageAttachment = class {
|
|
1313
|
+
constructor(input, caption, contentType) {
|
|
1314
|
+
this.input = input;
|
|
1315
|
+
this.caption = caption;
|
|
1316
|
+
this.contentType = contentType;
|
|
1317
|
+
}
|
|
1318
|
+
input;
|
|
1319
|
+
caption;
|
|
1320
|
+
contentType;
|
|
1321
|
+
[IMAGE_BRAND] = true;
|
|
1322
|
+
};
|
|
1323
|
+
function image(input, options = {}) {
|
|
1324
|
+
return new ImageAttachment(input, options.caption, options.contentType);
|
|
1325
|
+
}
|
|
1326
|
+
function isImageAttachment(value) {
|
|
1327
|
+
return typeof value === "object" && value !== null && value[IMAGE_BRAND] === true;
|
|
1328
|
+
}
|
|
1329
|
+
function toImageAttachment(value) {
|
|
1330
|
+
return isImageAttachment(value) ? value : new ImageAttachment(value);
|
|
1331
|
+
}
|
|
1305
1332
|
async function readImageFile(path) {
|
|
1306
1333
|
if (!isNode()) {
|
|
1307
1334
|
throw new Error("caminho de arquivo s\xF3 \xE9 suportado em Node");
|
|
@@ -1351,6 +1378,14 @@ function joinLogArgs(message, rest) {
|
|
|
1351
1378
|
if (rest.length === 0) return safeString(message);
|
|
1352
1379
|
return [message, ...rest].map(safeString).join(" ");
|
|
1353
1380
|
}
|
|
1381
|
+
function splitLogArgs(message, rest) {
|
|
1382
|
+
const args = [message, ...rest];
|
|
1383
|
+
const last = args[args.length - 1];
|
|
1384
|
+
if (!isImageAttachment(last)) return { text: joinLogArgs(message, rest) };
|
|
1385
|
+
const text = args.slice(0, -1);
|
|
1386
|
+
if (text.length === 0) return { text: last.caption !== void 0 ? safeString(last.caption) : "captura de tela", image: last };
|
|
1387
|
+
return { text: joinLogArgs(text[0], text.slice(1)), image: last };
|
|
1388
|
+
}
|
|
1354
1389
|
function printRunLog(task, level, message) {
|
|
1355
1390
|
const line = colorize(`[hobots:${task}] ${level.toUpperCase()}`, LOG_LEVEL_COLOR[level]);
|
|
1356
1391
|
const method2 = level === "warning" ? "warn" : level === "success" ? "info" : level;
|
|
@@ -1407,18 +1442,23 @@ var RunReporter = class {
|
|
|
1407
1442
|
MAX_CANCEL_CHECK_INTERVAL_MS,
|
|
1408
1443
|
Math.max(MIN_CANCEL_CHECK_INTERVAL_MS, cancelCheckIntervalMs ?? DEFAULT_CANCEL_CHECK_INTERVAL_MS)
|
|
1409
1444
|
);
|
|
1445
|
+
const logAt = (level) => (message, ...rest) => {
|
|
1446
|
+
const { text, image: image3 } = splitLogArgs(message, rest);
|
|
1447
|
+
this.log(level, text, image3);
|
|
1448
|
+
};
|
|
1410
1449
|
const log = {
|
|
1411
|
-
debug: (
|
|
1412
|
-
info: (
|
|
1413
|
-
warn: (
|
|
1414
|
-
error: (
|
|
1415
|
-
success: (
|
|
1450
|
+
debug: logAt("debug"),
|
|
1451
|
+
info: logAt("info"),
|
|
1452
|
+
warn: logAt("warning"),
|
|
1453
|
+
error: logAt("error"),
|
|
1454
|
+
success: logAt("success")
|
|
1416
1455
|
};
|
|
1417
1456
|
tx.setLifecycleSink((level, message) => this.push(level, message));
|
|
1457
|
+
tx.setAttachmentSink((image3, ts) => this.queueAttachment(image3, ts));
|
|
1418
1458
|
const checkCanceled = () => this.checkCanceled();
|
|
1419
1459
|
tx.setStepGuard(checkCanceled);
|
|
1420
1460
|
const eachSink = {
|
|
1421
|
-
report: (status, message, payload, id) => this.addItem(status, message, payload, id),
|
|
1461
|
+
report: (status, message, payload, id, image3) => this.addItem(status, message, payload, id, image3),
|
|
1422
1462
|
startStep: (op, description) => tx.startChild(op, description),
|
|
1423
1463
|
declareTotal: (total) => {
|
|
1424
1464
|
if (this.progressTotal == null) this.setProgress(this.progressCurrent, total);
|
|
@@ -1431,19 +1471,19 @@ var RunReporter = class {
|
|
|
1431
1471
|
const items = {
|
|
1432
1472
|
add: (item) => {
|
|
1433
1473
|
checkCanceled();
|
|
1434
|
-
this.addItem(item.status, item.message, item.payload, item.id);
|
|
1474
|
+
this.addItem(item.status, item.message, item.payload, item.id, item.image);
|
|
1435
1475
|
},
|
|
1436
1476
|
succeeded: (payload, options) => {
|
|
1437
1477
|
checkCanceled();
|
|
1438
|
-
this.addItem("succeeded", void 0, payload, options?.id);
|
|
1478
|
+
this.addItem("succeeded", void 0, payload, options?.id, options?.image);
|
|
1439
1479
|
},
|
|
1440
1480
|
failed: (message, payload, options) => {
|
|
1441
1481
|
checkCanceled();
|
|
1442
|
-
this.addItem("failed", message, payload, options?.id);
|
|
1482
|
+
this.addItem("failed", message, payload, options?.id, options?.image);
|
|
1443
1483
|
},
|
|
1444
1484
|
occurrence: (message, payload, options) => {
|
|
1445
1485
|
checkCanceled();
|
|
1446
|
-
this.addItem("occurrence", message, payload, options?.id);
|
|
1486
|
+
this.addItem("occurrence", message, payload, options?.id, options?.image);
|
|
1447
1487
|
},
|
|
1448
1488
|
each: (list, handler, options = {}) => eachItem(list, handler, options, eachSink)
|
|
1449
1489
|
};
|
|
@@ -1484,7 +1524,6 @@ var RunReporter = class {
|
|
|
1484
1524
|
signal,
|
|
1485
1525
|
throwIfCanceled: checkCanceled,
|
|
1486
1526
|
sleep: sleep2,
|
|
1487
|
-
attach: (image, options) => this.attach(image, options),
|
|
1488
1527
|
retry: (fn, options) => retryWithBackoff(fn, { ...options, signal, wait: sleep2 })
|
|
1489
1528
|
};
|
|
1490
1529
|
}
|
|
@@ -1575,15 +1614,21 @@ var RunReporter = class {
|
|
|
1575
1614
|
this.clearFlushTimer();
|
|
1576
1615
|
return this.takeLogs().slice(-MAX_LOGS_PER_PATCH);
|
|
1577
1616
|
}
|
|
1578
|
-
|
|
1617
|
+
/**
|
|
1618
|
+
* Linha de log da run com eco no terminal. `image` (opcional) vira o anexo da linha:
|
|
1619
|
+
* o id entra na entrada ANTES de qualquer I/O — é isso que mantém a ordem dos logs
|
|
1620
|
+
* intacta e o handler livre da latência do upload.
|
|
1621
|
+
*/
|
|
1622
|
+
log(level, message, image3) {
|
|
1579
1623
|
if (this.dead) return;
|
|
1580
1624
|
const scrubbed = scrubSecrets(safeString(message));
|
|
1581
|
-
|
|
1582
|
-
this.
|
|
1625
|
+
const ts = nowISO();
|
|
1626
|
+
const attachmentId = image3 === void 0 ? void 0 : this.queueAttachment(image3, ts);
|
|
1627
|
+
printRunLog(this.context.task, level, attachmentId ? `${scrubbed} [imagem]` : scrubbed);
|
|
1628
|
+
this.push(level, scrubbed, true, attachmentId ? [attachmentId] : void 0, ts);
|
|
1583
1629
|
}
|
|
1584
1630
|
/** Bufferiza uma entrada de log sem eco no terminal (cap, truncate, flush). */
|
|
1585
|
-
push(level, message, alreadyScrubbed = false, attachments) {
|
|
1586
|
-
const ts = nowISO();
|
|
1631
|
+
push(level, message, alreadyScrubbed = false, attachments, ts = nowISO()) {
|
|
1587
1632
|
if (this.dead) return ts;
|
|
1588
1633
|
const scrubbed = alreadyScrubbed ? message : scrubSecrets(message);
|
|
1589
1634
|
this.buffer.push({
|
|
@@ -1733,7 +1778,7 @@ var RunReporter = class {
|
|
|
1733
1778
|
* de status, o servidor aceita itens mesmo com a run cancelada (trabalho já feito),
|
|
1734
1779
|
* então um reporter morto não descarta: só um kill() de exit path descarta.
|
|
1735
1780
|
*/
|
|
1736
|
-
addItem(status, message, payload, id) {
|
|
1781
|
+
addItem(status, message, payload, id, image3) {
|
|
1737
1782
|
if (this.itemsClosed || this.itemsUnsupported) return;
|
|
1738
1783
|
if (this.itemCount >= MAX_ITEMS_PER_RUN) {
|
|
1739
1784
|
if (!this.itemsQuotaExceeded) {
|
|
@@ -1758,13 +1803,16 @@ var RunReporter = class {
|
|
|
1758
1803
|
scrubbedPayload = void 0;
|
|
1759
1804
|
}
|
|
1760
1805
|
}
|
|
1806
|
+
const ts = nowISO();
|
|
1807
|
+
const attachmentId = image3 === void 0 ? void 0 : this.queueAttachment(image3, ts);
|
|
1761
1808
|
this.itemsBuffer.push({
|
|
1762
1809
|
id: id ? truncate(String(id), 255) : uuid4(),
|
|
1763
1810
|
seq: this.itemSeq++,
|
|
1764
1811
|
status,
|
|
1765
1812
|
...message ? { message: truncate(scrubSecrets(safeString(message)), MAX_ITEM_MESSAGE_CHARS) } : {},
|
|
1766
1813
|
...scrubbedPayload ? { payload: scrubbedPayload } : {},
|
|
1767
|
-
ts
|
|
1814
|
+
ts,
|
|
1815
|
+
...attachmentId ? { attachments: [attachmentId] } : {}
|
|
1768
1816
|
});
|
|
1769
1817
|
if (this.itemsBuffer.length >= ITEMS_FLUSH_AT_BUFFERED) {
|
|
1770
1818
|
this.flushItems();
|
|
@@ -1844,59 +1892,51 @@ var RunReporter = class {
|
|
|
1844
1892
|
}
|
|
1845
1893
|
// ── Anexos ───────────────────────────────────────────────────────────────────
|
|
1846
1894
|
/**
|
|
1847
|
-
*
|
|
1848
|
-
*
|
|
1849
|
-
*
|
|
1850
|
-
*
|
|
1895
|
+
* Pendura uma imagem num evento da run (log, item ou step): faz as checagens que não
|
|
1896
|
+
* dependem de I/O (reporter morto, instalação sem anexos, cota), reserva o id e dispara
|
|
1897
|
+
* o upload em segundo plano. Devolve o id — já referenciável pelo evento — ou undefined
|
|
1898
|
+
* quando a imagem foi descartada (sempre com aviso no log da run). Nunca lança: todo
|
|
1899
|
+
* caminho de erro do upload vira aviso. O hint `attachment` de `hobots.captureException`
|
|
1900
|
+
* também cai aqui via `log()`.
|
|
1851
1901
|
*/
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
const
|
|
1855
|
-
|
|
1856
|
-
const rawMessage = options.message !== void 0 ? safeString(options.message) : void 0;
|
|
1857
|
-
const caption = rawCaption ? truncate(scrubSecrets(rawCaption), MAX_CAPTION_CHARS) : void 0;
|
|
1858
|
-
const message = rawMessage ?? rawCaption ?? "captura de tela";
|
|
1859
|
-
if (this.dead) return { id, ok: false, reason: "canceled" };
|
|
1860
|
-
if (this.attachmentsUnsupported) {
|
|
1861
|
-
this.log(level, message);
|
|
1862
|
-
return { id, ok: false, reason: "unsupported" };
|
|
1863
|
-
}
|
|
1902
|
+
queueAttachment(imageIn, ts) {
|
|
1903
|
+
if (this.dead) return void 0;
|
|
1904
|
+
const image3 = toImageAttachment(imageIn);
|
|
1905
|
+
if (this.attachmentsUnsupported) return void 0;
|
|
1864
1906
|
if (this.attachmentCount >= MAX_ATTACHMENTS_PER_RUN) {
|
|
1865
|
-
this.
|
|
1866
|
-
|
|
1867
|
-
|
|
1907
|
+
if (this.attachmentCount === MAX_ATTACHMENTS_PER_RUN) {
|
|
1908
|
+
this.attachmentCount++;
|
|
1909
|
+
this.log("warning", `imagem descartada: limite de ${MAX_ATTACHMENTS_PER_RUN} por execu\xE7\xE3o atingido`);
|
|
1910
|
+
}
|
|
1911
|
+
return void 0;
|
|
1868
1912
|
}
|
|
1869
1913
|
this.attachmentCount++;
|
|
1870
|
-
const
|
|
1871
|
-
|
|
1872
|
-
const
|
|
1873
|
-
|
|
1874
|
-
const result = new Promise((resolve2) => {
|
|
1875
|
-
settle = resolve2;
|
|
1876
|
-
});
|
|
1877
|
-
const work = this.uploadAttachment(id, image, { caption, logTs, contentType: options.contentType }).then((reason) => settle(reason ? { id, ok: false, reason } : { id, ok: true })).catch(() => settle({ id, ok: false, reason: "upload-failed" }));
|
|
1914
|
+
const id = uuid4();
|
|
1915
|
+
const rawCaption = image3.caption !== void 0 ? safeString(image3.caption) : void 0;
|
|
1916
|
+
const caption = rawCaption ? truncate(scrubSecrets(rawCaption), MAX_CAPTION_CHARS) : void 0;
|
|
1917
|
+
const work = this.uploadAttachment(id, image3.input, { caption, logTs: ts, contentType: image3.contentType }).then(() => void 0).catch(() => void 0);
|
|
1878
1918
|
this.pendingAttachments.add(work);
|
|
1879
1919
|
void work.finally(() => this.pendingAttachments.delete(work));
|
|
1880
|
-
return
|
|
1920
|
+
return id;
|
|
1881
1921
|
}
|
|
1882
1922
|
/** Retorna o motivo da falha, ou undefined em sucesso. */
|
|
1883
|
-
async uploadAttachment(id,
|
|
1923
|
+
async uploadAttachment(id, image3, meta) {
|
|
1884
1924
|
let bytes;
|
|
1885
1925
|
try {
|
|
1886
|
-
bytes = await toBytes(
|
|
1926
|
+
bytes = await toBytes(image3);
|
|
1887
1927
|
} catch (error) {
|
|
1888
|
-
this.log("warning", `
|
|
1928
|
+
this.log("warning", `imagem n\xE3o p\xF4de ser lida: ${String(error)}`);
|
|
1889
1929
|
return "read-failed";
|
|
1890
1930
|
}
|
|
1891
1931
|
const contentType = sniffContentType(bytes) ?? meta.contentType;
|
|
1892
1932
|
if (!contentType) {
|
|
1893
|
-
this.log("warning", "
|
|
1933
|
+
this.log("warning", "imagem descartada: formato n\xE3o reconhecido (aceitos: PNG, JPEG, WEBP)");
|
|
1894
1934
|
return "unsupported-format";
|
|
1895
1935
|
}
|
|
1896
1936
|
if (bytes.byteLength > MAX_ATTACHMENT_BYTES) {
|
|
1897
1937
|
this.log(
|
|
1898
1938
|
"warning",
|
|
1899
|
-
`
|
|
1939
|
+
`imagem descartada: ${Math.round(bytes.byteLength / 1024)} KB excede o limite de ${MAX_ATTACHMENT_BYTES / 1024 / 1024} MB`
|
|
1900
1940
|
);
|
|
1901
1941
|
return "too-large";
|
|
1902
1942
|
}
|
|
@@ -1927,11 +1967,11 @@ var RunReporter = class {
|
|
|
1927
1967
|
return "unsupported";
|
|
1928
1968
|
}
|
|
1929
1969
|
if (error instanceof TasksApiError && error.status === 429) {
|
|
1930
|
-
this.log("warning", "
|
|
1970
|
+
this.log("warning", "imagem descartada: limite de anexos por execu\xE7\xE3o atingido no servidor");
|
|
1931
1971
|
return "quota";
|
|
1932
1972
|
}
|
|
1933
1973
|
if (this.abortController.signal.aborted) return "canceled";
|
|
1934
|
-
this.log("warning", `falha ao enviar
|
|
1974
|
+
this.log("warning", `falha ao enviar a imagem: ${String(error)}`);
|
|
1935
1975
|
void this.api.confirmAttachment(this.runId, id, { status: "failed" }).catch(() => {
|
|
1936
1976
|
});
|
|
1937
1977
|
return "upload-failed";
|
|
@@ -2095,23 +2135,39 @@ var Step = class {
|
|
|
2095
2135
|
/**
|
|
2096
2136
|
* Desfecho da step. `message` (opcional) é o motivo legível — vai em `data.message`
|
|
2097
2137
|
* e aparece na Central (performance e banner da execução) e no log de FINISH.
|
|
2138
|
+
* `options.image` pendura um print do estado da tela em `data.attachments`.
|
|
2098
2139
|
*/
|
|
2099
|
-
setStatus(status, message) {
|
|
2140
|
+
setStatus(status, message, options) {
|
|
2100
2141
|
this.status = status;
|
|
2101
2142
|
if (message) this.setData("message", stepMessage(message));
|
|
2143
|
+
if (options?.image !== void 0) this.attachImage(options.image);
|
|
2102
2144
|
return this;
|
|
2103
2145
|
}
|
|
2104
2146
|
/**
|
|
2105
2147
|
* Marca a step como `error` guardando o motivo em `data.error` ({ type, message }).
|
|
2106
2148
|
* Aceita qualquer valor lançado; `message` sobrescreve a mensagem do erro.
|
|
2149
|
+
* `options.image` pendura um print do estado da tela em `data.attachments`.
|
|
2107
2150
|
*/
|
|
2108
|
-
setError(error, message) {
|
|
2151
|
+
setError(error, message, options) {
|
|
2109
2152
|
this.status = "error";
|
|
2110
2153
|
const type = error instanceof Error ? error.name : typeof error === "object" && error !== null ? "Error" : void 0;
|
|
2111
2154
|
const raw = message ?? (error instanceof Error ? error.message || error.name : String(error));
|
|
2112
2155
|
this.setData("error", { ...type ? { type } : {}, message: stepMessage(raw) });
|
|
2156
|
+
if (options?.image !== void 0) this.attachImage(options.image);
|
|
2113
2157
|
return this;
|
|
2114
2158
|
}
|
|
2159
|
+
/**
|
|
2160
|
+
* Dispara o upload pela run (sink do RunReporter) e referencia o anexo em
|
|
2161
|
+
* `data.attachments` (chave reservada; teto igual ao das linhas de log). Fora de uma
|
|
2162
|
+
* run não há onde pendurar a imagem — descarta com aviso único.
|
|
2163
|
+
*/
|
|
2164
|
+
attachImage(image3) {
|
|
2165
|
+
const current = Array.isArray(this.data?.attachments) ? this.data.attachments : [];
|
|
2166
|
+
if (current.length >= MAX_ATTACHMENTS_PER_LOG_ENTRY) return;
|
|
2167
|
+
const id = this.transaction.attachImage(image3);
|
|
2168
|
+
if (!id) return;
|
|
2169
|
+
this.setData("attachments", [...current, id]);
|
|
2170
|
+
}
|
|
2115
2171
|
/** Motivo registrado via setStatus(…, message) ou setError — usado no log de FINISH. */
|
|
2116
2172
|
reason() {
|
|
2117
2173
|
const error = this.data?.error;
|
|
@@ -2172,6 +2228,8 @@ var Transaction = class {
|
|
|
2172
2228
|
tags = {};
|
|
2173
2229
|
finished = false;
|
|
2174
2230
|
lifecycleSink;
|
|
2231
|
+
attachmentSink;
|
|
2232
|
+
warnedNoAttachmentSink = false;
|
|
2175
2233
|
// instalado pelo RunReporter: abrir uma step é um ponto de parada cooperativo (lança RunCanceled)
|
|
2176
2234
|
stepGuard;
|
|
2177
2235
|
// eventos emitidos antes do sink existir (o START da transaction sai no construtor,
|
|
@@ -2202,6 +2260,19 @@ var Transaction = class {
|
|
|
2202
2260
|
this.lifecycleBacklog = [];
|
|
2203
2261
|
for (const [level, message] of backlog) sink(level, message);
|
|
2204
2262
|
}
|
|
2263
|
+
/** Conecta o pipeline de anexos da run — é o que dá imagem às steps. */
|
|
2264
|
+
setAttachmentSink(sink) {
|
|
2265
|
+
this.attachmentSink = sink;
|
|
2266
|
+
}
|
|
2267
|
+
/** Imagem de uma step → id do anexo pela run; sem run, descarta com aviso único. */
|
|
2268
|
+
attachImage(image3) {
|
|
2269
|
+
if (this.attachmentSink) return this.attachmentSink(image3, nowISO());
|
|
2270
|
+
if (!this.warnedNoAttachmentSink) {
|
|
2271
|
+
this.warnedNoAttachmentSink = true;
|
|
2272
|
+
console.warn("[hobots] imagem de step descartada \u2014 s\xF3 \xE9 suportada dentro de uma execu\xE7\xE3o de task");
|
|
2273
|
+
}
|
|
2274
|
+
return void 0;
|
|
2275
|
+
}
|
|
2205
2276
|
/**
|
|
2206
2277
|
* Conecta a checagem de cancelamento chamada antes de cada step nova — é assim que
|
|
2207
2278
|
* `hobots.transaction.startChild()` (e `step.startChild()`) param uma run cancelada.
|
|
@@ -2969,13 +3040,8 @@ function forwardAttachment(rt, attachment, message, type) {
|
|
|
2969
3040
|
rt.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o de task");
|
|
2970
3041
|
return;
|
|
2971
3042
|
}
|
|
2972
|
-
const withMeta = typeof attachment === "object" && "image" in attachment ? attachment :
|
|
2973
|
-
|
|
2974
|
-
message,
|
|
2975
|
-
level: toRunLogLevel(type),
|
|
2976
|
-
caption: withMeta.caption,
|
|
2977
|
-
contentType: withMeta.contentType
|
|
2978
|
-
});
|
|
3043
|
+
const withMeta = typeof attachment === "object" && !isImageAttachment(attachment) && "image" in attachment ? image(attachment.image, { caption: attachment.caption, contentType: attachment.contentType }) : attachment;
|
|
3044
|
+
reporter.log(toRunLogLevel(type), message, withMeta);
|
|
2979
3045
|
}
|
|
2980
3046
|
function captureException(error, hint) {
|
|
2981
3047
|
const rt = runtime;
|
|
@@ -3022,12 +3088,16 @@ function warnOutsideRun(member) {
|
|
|
3022
3088
|
else console.warn(`[hobots] ${message}`);
|
|
3023
3089
|
}
|
|
3024
3090
|
var FALLBACK_TASK = "hobots";
|
|
3091
|
+
var fallbackLogAt = (level) => (message, ...rest) => {
|
|
3092
|
+
const { text, image: dropped } = splitLogArgs(message, rest);
|
|
3093
|
+
printRunLog(FALLBACK_TASK, level, dropped ? `${text} [imagem descartada]` : text);
|
|
3094
|
+
};
|
|
3025
3095
|
var fallbackLog = {
|
|
3026
|
-
debug: (
|
|
3027
|
-
info: (
|
|
3028
|
-
warn: (
|
|
3029
|
-
error: (
|
|
3030
|
-
success: (
|
|
3096
|
+
debug: fallbackLogAt("debug"),
|
|
3097
|
+
info: fallbackLogAt("info"),
|
|
3098
|
+
warn: fallbackLogAt("warning"),
|
|
3099
|
+
error: fallbackLogAt("error"),
|
|
3100
|
+
success: fallbackLogAt("success")
|
|
3031
3101
|
};
|
|
3032
3102
|
var noop = () => void 0;
|
|
3033
3103
|
var fallbackEachSink = {
|
|
@@ -3073,9 +3143,6 @@ var hobots = Object.defineProperties({}, {
|
|
|
3073
3143
|
signal: getter("signal", () => neverAborts),
|
|
3074
3144
|
throwIfCanceled: method(() => resolve("throwIfCanceled")?.throwIfCanceled()),
|
|
3075
3145
|
sleep: method((ms) => resolve("sleep")?.sleep(ms) ?? sleep(ms)),
|
|
3076
|
-
attach: method(
|
|
3077
|
-
(image, options) => resolve("attach")?.attach(image, options) ?? Promise.resolve({ id: "", ok: false, reason: "canceled" })
|
|
3078
|
-
),
|
|
3079
3146
|
retry: method((fn, options) => {
|
|
3080
3147
|
const reporter = currentReporter();
|
|
3081
3148
|
return reporter ? reporter.context.retry(fn, options) : retryWithBackoff(fn, options);
|
|
@@ -3090,7 +3157,8 @@ var hobots = Object.defineProperties({}, {
|
|
|
3090
3157
|
installGlobalHobots();
|
|
3091
3158
|
}),
|
|
3092
3159
|
close: method((timeoutMs) => close(timeoutMs)),
|
|
3093
|
-
captureException: method((error, hint) => captureException(error, hint))
|
|
3160
|
+
captureException: method((error, hint) => captureException(error, hint)),
|
|
3161
|
+
image: method(image)
|
|
3094
3162
|
});
|
|
3095
3163
|
function installGlobalHobots() {
|
|
3096
3164
|
if (globalThis.hobots === hobots) return;
|
|
@@ -3101,6 +3169,7 @@ var start2 = hobots.start;
|
|
|
3101
3169
|
var close2 = hobots.close;
|
|
3102
3170
|
var captureException2 = hobots.captureException;
|
|
3103
3171
|
var retry = hobots.retry;
|
|
3172
|
+
var image2 = hobots.image;
|
|
3104
3173
|
|
|
3105
3174
|
// src/index.ts
|
|
3106
3175
|
var src_default = hobots;
|