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/dist/index.js
CHANGED
|
@@ -57,7 +57,7 @@ function scrubSecrets(text) {
|
|
|
57
57
|
|
|
58
58
|
// src/utils.ts
|
|
59
59
|
var SDK_NAME = "hobots";
|
|
60
|
-
var SDK_VERSION = true ? "3.
|
|
60
|
+
var SDK_VERSION = true ? "3.4.0" : "0.0.0-dev";
|
|
61
61
|
function isNode() {
|
|
62
62
|
return typeof process !== "undefined" && typeof process.versions?.node === "string";
|
|
63
63
|
}
|
|
@@ -312,6 +312,7 @@ var Transport = class {
|
|
|
312
312
|
try {
|
|
313
313
|
response = await fetchWithRetry(
|
|
314
314
|
() => fetch(this.endpoint, {
|
|
315
|
+
redirect: "error",
|
|
315
316
|
method: "POST",
|
|
316
317
|
headers: { "content-type": "application/json", authorization: `Bearer ${this.key}` },
|
|
317
318
|
body: JSON.stringify(envelope),
|
|
@@ -489,7 +490,6 @@ function envBaseUrl() {
|
|
|
489
490
|
function isLoopbackHost(hostname2) {
|
|
490
491
|
return hostname2 === "localhost" || hostname2.endsWith(".localhost") || hostname2.startsWith("127.") || hostname2 === "[::1]" || hostname2 === "::1" || hostname2 === "0.0.0.0";
|
|
491
492
|
}
|
|
492
|
-
var warnedInsecureBaseUrl = false;
|
|
493
493
|
function validateBaseUrl(raw, prefix) {
|
|
494
494
|
let url;
|
|
495
495
|
try {
|
|
@@ -503,11 +503,8 @@ function validateBaseUrl(raw, prefix) {
|
|
|
503
503
|
if (url.username || url.password) {
|
|
504
504
|
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`);
|
|
505
505
|
}
|
|
506
|
-
if (url.protocol === "http:" && !isLoopbackHost(url.hostname)
|
|
507
|
-
|
|
508
|
-
console.warn(
|
|
509
|
-
`${prefix} HOBOTS_API_URL usa http para o host remoto "${url.hostname}" \u2014 o Client Secret trafega sem TLS`
|
|
510
|
-
);
|
|
506
|
+
if (url.protocol === "http:" && !isLoopbackHost(url.hostname)) {
|
|
507
|
+
throw new Error(`${prefix} HOBOTS_API_URL usa http para o host remoto "${url.hostname}" \u2014 o Client Secret trafegaria sem TLS; use https`);
|
|
511
508
|
}
|
|
512
509
|
return raw.replace(/\/+$/, "");
|
|
513
510
|
}
|
|
@@ -871,6 +868,7 @@ var TasksApi = class {
|
|
|
871
868
|
let response;
|
|
872
869
|
try {
|
|
873
870
|
response = await fetch(`${this.baseUrl}/poll`, {
|
|
871
|
+
redirect: "error",
|
|
874
872
|
method: "POST",
|
|
875
873
|
headers: this.headers(),
|
|
876
874
|
body: JSON.stringify(request),
|
|
@@ -894,6 +892,7 @@ var TasksApi = class {
|
|
|
894
892
|
let response;
|
|
895
893
|
try {
|
|
896
894
|
response = await fetch(`${this.baseUrl}/runs`, {
|
|
895
|
+
redirect: "error",
|
|
897
896
|
method: "POST",
|
|
898
897
|
headers: this.headers(),
|
|
899
898
|
body: JSON.stringify(request),
|
|
@@ -944,6 +943,7 @@ var TasksApi = class {
|
|
|
944
943
|
try {
|
|
945
944
|
response = await fetchWithRetry(
|
|
946
945
|
() => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
|
|
946
|
+
redirect: "error",
|
|
947
947
|
method: "PATCH",
|
|
948
948
|
headers: this.headers(),
|
|
949
949
|
body: JSON.stringify(update),
|
|
@@ -965,6 +965,7 @@ var TasksApi = class {
|
|
|
965
965
|
*/
|
|
966
966
|
async updateRunOnce(runId, update, timeoutMs = 2e3) {
|
|
967
967
|
await fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
|
|
968
|
+
redirect: "error",
|
|
968
969
|
method: "PATCH",
|
|
969
970
|
headers: this.headers(),
|
|
970
971
|
body: JSON.stringify(update),
|
|
@@ -981,6 +982,7 @@ var TasksApi = class {
|
|
|
981
982
|
try {
|
|
982
983
|
response = await fetchWithRetry(
|
|
983
984
|
() => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}/items`, {
|
|
985
|
+
redirect: "error",
|
|
984
986
|
method: "POST",
|
|
985
987
|
headers: this.headers(),
|
|
986
988
|
body: JSON.stringify(request),
|
|
@@ -1010,6 +1012,7 @@ var TasksApi = class {
|
|
|
1010
1012
|
UPDATE_TIMEOUT_MS,
|
|
1011
1013
|
signal,
|
|
1012
1014
|
(timeoutSignal) => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}/attachments`, {
|
|
1015
|
+
redirect: "error",
|
|
1013
1016
|
method: "POST",
|
|
1014
1017
|
headers: this.headers(),
|
|
1015
1018
|
body: JSON.stringify(request),
|
|
@@ -1042,6 +1045,7 @@ var TasksApi = class {
|
|
|
1042
1045
|
UPLOAD_TIMEOUT_MS,
|
|
1043
1046
|
signal,
|
|
1044
1047
|
(timeoutSignal) => fetch(url, {
|
|
1048
|
+
redirect: "error",
|
|
1045
1049
|
method: "PUT",
|
|
1046
1050
|
headers,
|
|
1047
1051
|
// BodyInit não aceita Uint8Array<ArrayBufferLike> em todos os lib.dom; o buffer é o mesmo
|
|
@@ -1060,6 +1064,7 @@ var TasksApi = class {
|
|
|
1060
1064
|
const response = await fetch(
|
|
1061
1065
|
`${this.baseUrl}/runs/${encodeURIComponent(runId)}/attachments/${encodeURIComponent(attachmentId)}`,
|
|
1062
1066
|
{
|
|
1067
|
+
redirect: "error",
|
|
1063
1068
|
method: "PATCH",
|
|
1064
1069
|
headers: this.headers(),
|
|
1065
1070
|
body: JSON.stringify(request),
|
|
@@ -1142,22 +1147,22 @@ var ItemHandle = class {
|
|
|
1142
1147
|
sink;
|
|
1143
1148
|
reported = false;
|
|
1144
1149
|
status;
|
|
1145
|
-
succeeded(payload) {
|
|
1146
|
-
this.record("succeeded", void 0, payload);
|
|
1150
|
+
succeeded(payload, options) {
|
|
1151
|
+
this.record("succeeded", void 0, payload, options?.image);
|
|
1147
1152
|
}
|
|
1148
|
-
failed(message, payload) {
|
|
1149
|
-
this.record("failed", message, payload);
|
|
1153
|
+
failed(message, payload, options) {
|
|
1154
|
+
this.record("failed", message, payload, options?.image);
|
|
1150
1155
|
if (this.step && this.step.status !== "error") this.step.setStatus("error", message);
|
|
1151
1156
|
}
|
|
1152
|
-
occurrence(message, payload) {
|
|
1153
|
-
this.record("occurrence", message, payload);
|
|
1157
|
+
occurrence(message, payload, options) {
|
|
1158
|
+
this.record("occurrence", message, payload, options?.image);
|
|
1154
1159
|
if (this.step && this.step.status === "ok") this.step.setStatus("warning", message);
|
|
1155
1160
|
}
|
|
1156
|
-
record(status, message, payload) {
|
|
1161
|
+
record(status, message, payload, image3) {
|
|
1157
1162
|
this.reported = true;
|
|
1158
1163
|
this.status = status;
|
|
1159
1164
|
const merged = { ...this.basePayload ?? {}, ...payload ?? {} };
|
|
1160
|
-
this.sink.report(status, message, Object.keys(merged).length > 0 ? merged : void 0, this.id);
|
|
1165
|
+
this.sink.report(status, message, Object.keys(merged).length > 0 ? merged : void 0, this.id, image3);
|
|
1161
1166
|
}
|
|
1162
1167
|
};
|
|
1163
1168
|
function resolveId(options, element, index) {
|
|
@@ -1237,6 +1242,27 @@ var MAX_ATTACHMENTS_PER_LOG_ENTRY = WIRE_LIMITS.MAX_ATTACHMENTS_PER_LOG_ENTRY;
|
|
|
1237
1242
|
var MAX_CONCURRENT_UPLOADS = 3;
|
|
1238
1243
|
var MAX_CAPTION_CHARS = WIRE_LIMITS.MAX_CAPTION_CHARS;
|
|
1239
1244
|
var ATTACHMENT_DRAIN_MS = 15e3;
|
|
1245
|
+
var IMAGE_BRAND = /* @__PURE__ */ Symbol.for("hobots.image");
|
|
1246
|
+
var ImageAttachment = class {
|
|
1247
|
+
constructor(input, caption, contentType) {
|
|
1248
|
+
this.input = input;
|
|
1249
|
+
this.caption = caption;
|
|
1250
|
+
this.contentType = contentType;
|
|
1251
|
+
}
|
|
1252
|
+
input;
|
|
1253
|
+
caption;
|
|
1254
|
+
contentType;
|
|
1255
|
+
[IMAGE_BRAND] = true;
|
|
1256
|
+
};
|
|
1257
|
+
function image(input, options = {}) {
|
|
1258
|
+
return new ImageAttachment(input, options.caption, options.contentType);
|
|
1259
|
+
}
|
|
1260
|
+
function isImageAttachment(value) {
|
|
1261
|
+
return typeof value === "object" && value !== null && value[IMAGE_BRAND] === true;
|
|
1262
|
+
}
|
|
1263
|
+
function toImageAttachment(value) {
|
|
1264
|
+
return isImageAttachment(value) ? value : new ImageAttachment(value);
|
|
1265
|
+
}
|
|
1240
1266
|
async function readImageFile(path) {
|
|
1241
1267
|
if (!isNode()) {
|
|
1242
1268
|
throw new Error("caminho de arquivo s\xF3 \xE9 suportado em Node");
|
|
@@ -1286,6 +1312,14 @@ function joinLogArgs(message, rest) {
|
|
|
1286
1312
|
if (rest.length === 0) return safeString(message);
|
|
1287
1313
|
return [message, ...rest].map(safeString).join(" ");
|
|
1288
1314
|
}
|
|
1315
|
+
function splitLogArgs(message, rest) {
|
|
1316
|
+
const args = [message, ...rest];
|
|
1317
|
+
const last = args[args.length - 1];
|
|
1318
|
+
if (!isImageAttachment(last)) return { text: joinLogArgs(message, rest) };
|
|
1319
|
+
const text = args.slice(0, -1);
|
|
1320
|
+
if (text.length === 0) return { text: last.caption !== void 0 ? safeString(last.caption) : "captura de tela", image: last };
|
|
1321
|
+
return { text: joinLogArgs(text[0], text.slice(1)), image: last };
|
|
1322
|
+
}
|
|
1289
1323
|
function printRunLog(task, level, message) {
|
|
1290
1324
|
const line = colorize(`[hobots:${task}] ${level.toUpperCase()}`, LOG_LEVEL_COLOR[level]);
|
|
1291
1325
|
const method2 = level === "warning" ? "warn" : level === "success" ? "info" : level;
|
|
@@ -1342,18 +1376,23 @@ var RunReporter = class {
|
|
|
1342
1376
|
MAX_CANCEL_CHECK_INTERVAL_MS,
|
|
1343
1377
|
Math.max(MIN_CANCEL_CHECK_INTERVAL_MS, cancelCheckIntervalMs ?? DEFAULT_CANCEL_CHECK_INTERVAL_MS)
|
|
1344
1378
|
);
|
|
1379
|
+
const logAt = (level) => (message, ...rest) => {
|
|
1380
|
+
const { text, image: image3 } = splitLogArgs(message, rest);
|
|
1381
|
+
this.log(level, text, image3);
|
|
1382
|
+
};
|
|
1345
1383
|
const log = {
|
|
1346
|
-
debug: (
|
|
1347
|
-
info: (
|
|
1348
|
-
warn: (
|
|
1349
|
-
error: (
|
|
1350
|
-
success: (
|
|
1384
|
+
debug: logAt("debug"),
|
|
1385
|
+
info: logAt("info"),
|
|
1386
|
+
warn: logAt("warning"),
|
|
1387
|
+
error: logAt("error"),
|
|
1388
|
+
success: logAt("success")
|
|
1351
1389
|
};
|
|
1352
1390
|
tx.setLifecycleSink((level, message) => this.push(level, message));
|
|
1391
|
+
tx.setAttachmentSink((image3, ts) => this.queueAttachment(image3, ts));
|
|
1353
1392
|
const checkCanceled = () => this.checkCanceled();
|
|
1354
1393
|
tx.setStepGuard(checkCanceled);
|
|
1355
1394
|
const eachSink = {
|
|
1356
|
-
report: (status, message, payload, id) => this.addItem(status, message, payload, id),
|
|
1395
|
+
report: (status, message, payload, id, image3) => this.addItem(status, message, payload, id, image3),
|
|
1357
1396
|
startStep: (op, description) => tx.startChild(op, description),
|
|
1358
1397
|
declareTotal: (total) => {
|
|
1359
1398
|
if (this.progressTotal == null) this.setProgress(this.progressCurrent, total);
|
|
@@ -1366,19 +1405,19 @@ var RunReporter = class {
|
|
|
1366
1405
|
const items = {
|
|
1367
1406
|
add: (item) => {
|
|
1368
1407
|
checkCanceled();
|
|
1369
|
-
this.addItem(item.status, item.message, item.payload, item.id);
|
|
1408
|
+
this.addItem(item.status, item.message, item.payload, item.id, item.image);
|
|
1370
1409
|
},
|
|
1371
1410
|
succeeded: (payload, options) => {
|
|
1372
1411
|
checkCanceled();
|
|
1373
|
-
this.addItem("succeeded", void 0, payload, options?.id);
|
|
1412
|
+
this.addItem("succeeded", void 0, payload, options?.id, options?.image);
|
|
1374
1413
|
},
|
|
1375
1414
|
failed: (message, payload, options) => {
|
|
1376
1415
|
checkCanceled();
|
|
1377
|
-
this.addItem("failed", message, payload, options?.id);
|
|
1416
|
+
this.addItem("failed", message, payload, options?.id, options?.image);
|
|
1378
1417
|
},
|
|
1379
1418
|
occurrence: (message, payload, options) => {
|
|
1380
1419
|
checkCanceled();
|
|
1381
|
-
this.addItem("occurrence", message, payload, options?.id);
|
|
1420
|
+
this.addItem("occurrence", message, payload, options?.id, options?.image);
|
|
1382
1421
|
},
|
|
1383
1422
|
each: (list, handler, options = {}) => eachItem(list, handler, options, eachSink)
|
|
1384
1423
|
};
|
|
@@ -1419,7 +1458,6 @@ var RunReporter = class {
|
|
|
1419
1458
|
signal,
|
|
1420
1459
|
throwIfCanceled: checkCanceled,
|
|
1421
1460
|
sleep: sleep2,
|
|
1422
|
-
attach: (image, options) => this.attach(image, options),
|
|
1423
1461
|
retry: (fn, options) => retryWithBackoff(fn, { ...options, signal, wait: sleep2 })
|
|
1424
1462
|
};
|
|
1425
1463
|
}
|
|
@@ -1510,15 +1548,21 @@ var RunReporter = class {
|
|
|
1510
1548
|
this.clearFlushTimer();
|
|
1511
1549
|
return this.takeLogs().slice(-MAX_LOGS_PER_PATCH);
|
|
1512
1550
|
}
|
|
1513
|
-
|
|
1551
|
+
/**
|
|
1552
|
+
* Linha de log da run com eco no terminal. `image` (opcional) vira o anexo da linha:
|
|
1553
|
+
* o id entra na entrada ANTES de qualquer I/O — é isso que mantém a ordem dos logs
|
|
1554
|
+
* intacta e o handler livre da latência do upload.
|
|
1555
|
+
*/
|
|
1556
|
+
log(level, message, image3) {
|
|
1514
1557
|
if (this.dead) return;
|
|
1515
1558
|
const scrubbed = scrubSecrets(safeString(message));
|
|
1516
|
-
|
|
1517
|
-
this.
|
|
1559
|
+
const ts = nowISO();
|
|
1560
|
+
const attachmentId = image3 === void 0 ? void 0 : this.queueAttachment(image3, ts);
|
|
1561
|
+
printRunLog(this.context.task, level, attachmentId ? `${scrubbed} [imagem]` : scrubbed);
|
|
1562
|
+
this.push(level, scrubbed, true, attachmentId ? [attachmentId] : void 0, ts);
|
|
1518
1563
|
}
|
|
1519
1564
|
/** Bufferiza uma entrada de log sem eco no terminal (cap, truncate, flush). */
|
|
1520
|
-
push(level, message, alreadyScrubbed = false, attachments) {
|
|
1521
|
-
const ts = nowISO();
|
|
1565
|
+
push(level, message, alreadyScrubbed = false, attachments, ts = nowISO()) {
|
|
1522
1566
|
if (this.dead) return ts;
|
|
1523
1567
|
const scrubbed = alreadyScrubbed ? message : scrubSecrets(message);
|
|
1524
1568
|
this.buffer.push({
|
|
@@ -1668,7 +1712,7 @@ var RunReporter = class {
|
|
|
1668
1712
|
* de status, o servidor aceita itens mesmo com a run cancelada (trabalho já feito),
|
|
1669
1713
|
* então um reporter morto não descarta: só um kill() de exit path descarta.
|
|
1670
1714
|
*/
|
|
1671
|
-
addItem(status, message, payload, id) {
|
|
1715
|
+
addItem(status, message, payload, id, image3) {
|
|
1672
1716
|
if (this.itemsClosed || this.itemsUnsupported) return;
|
|
1673
1717
|
if (this.itemCount >= MAX_ITEMS_PER_RUN) {
|
|
1674
1718
|
if (!this.itemsQuotaExceeded) {
|
|
@@ -1693,13 +1737,16 @@ var RunReporter = class {
|
|
|
1693
1737
|
scrubbedPayload = void 0;
|
|
1694
1738
|
}
|
|
1695
1739
|
}
|
|
1740
|
+
const ts = nowISO();
|
|
1741
|
+
const attachmentId = image3 === void 0 ? void 0 : this.queueAttachment(image3, ts);
|
|
1696
1742
|
this.itemsBuffer.push({
|
|
1697
1743
|
id: id ? truncate(String(id), 255) : uuid4(),
|
|
1698
1744
|
seq: this.itemSeq++,
|
|
1699
1745
|
status,
|
|
1700
1746
|
...message ? { message: truncate(scrubSecrets(safeString(message)), MAX_ITEM_MESSAGE_CHARS) } : {},
|
|
1701
1747
|
...scrubbedPayload ? { payload: scrubbedPayload } : {},
|
|
1702
|
-
ts
|
|
1748
|
+
ts,
|
|
1749
|
+
...attachmentId ? { attachments: [attachmentId] } : {}
|
|
1703
1750
|
});
|
|
1704
1751
|
if (this.itemsBuffer.length >= ITEMS_FLUSH_AT_BUFFERED) {
|
|
1705
1752
|
this.flushItems();
|
|
@@ -1779,59 +1826,51 @@ var RunReporter = class {
|
|
|
1779
1826
|
}
|
|
1780
1827
|
// ── Anexos ───────────────────────────────────────────────────────────────────
|
|
1781
1828
|
/**
|
|
1782
|
-
*
|
|
1783
|
-
*
|
|
1784
|
-
*
|
|
1785
|
-
*
|
|
1829
|
+
* Pendura uma imagem num evento da run (log, item ou step): faz as checagens que não
|
|
1830
|
+
* dependem de I/O (reporter morto, instalação sem anexos, cota), reserva o id e dispara
|
|
1831
|
+
* o upload em segundo plano. Devolve o id — já referenciável pelo evento — ou undefined
|
|
1832
|
+
* quando a imagem foi descartada (sempre com aviso no log da run). Nunca lança: todo
|
|
1833
|
+
* caminho de erro do upload vira aviso. O hint `attachment` de `hobots.captureException`
|
|
1834
|
+
* também cai aqui via `log()`.
|
|
1786
1835
|
*/
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
const
|
|
1790
|
-
|
|
1791
|
-
const rawMessage = options.message !== void 0 ? safeString(options.message) : void 0;
|
|
1792
|
-
const caption = rawCaption ? truncate(scrubSecrets(rawCaption), MAX_CAPTION_CHARS) : void 0;
|
|
1793
|
-
const message = rawMessage ?? rawCaption ?? "captura de tela";
|
|
1794
|
-
if (this.dead) return { id, ok: false, reason: "canceled" };
|
|
1795
|
-
if (this.attachmentsUnsupported) {
|
|
1796
|
-
this.log(level, message);
|
|
1797
|
-
return { id, ok: false, reason: "unsupported" };
|
|
1798
|
-
}
|
|
1836
|
+
queueAttachment(imageIn, ts) {
|
|
1837
|
+
if (this.dead) return void 0;
|
|
1838
|
+
const image3 = toImageAttachment(imageIn);
|
|
1839
|
+
if (this.attachmentsUnsupported) return void 0;
|
|
1799
1840
|
if (this.attachmentCount >= MAX_ATTACHMENTS_PER_RUN) {
|
|
1800
|
-
this.
|
|
1801
|
-
|
|
1802
|
-
|
|
1841
|
+
if (this.attachmentCount === MAX_ATTACHMENTS_PER_RUN) {
|
|
1842
|
+
this.attachmentCount++;
|
|
1843
|
+
this.log("warning", `imagem descartada: limite de ${MAX_ATTACHMENTS_PER_RUN} por execu\xE7\xE3o atingido`);
|
|
1844
|
+
}
|
|
1845
|
+
return void 0;
|
|
1803
1846
|
}
|
|
1804
1847
|
this.attachmentCount++;
|
|
1805
|
-
const
|
|
1806
|
-
|
|
1807
|
-
const
|
|
1808
|
-
|
|
1809
|
-
const result = new Promise((resolve2) => {
|
|
1810
|
-
settle = resolve2;
|
|
1811
|
-
});
|
|
1812
|
-
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" }));
|
|
1848
|
+
const id = uuid4();
|
|
1849
|
+
const rawCaption = image3.caption !== void 0 ? safeString(image3.caption) : void 0;
|
|
1850
|
+
const caption = rawCaption ? truncate(scrubSecrets(rawCaption), MAX_CAPTION_CHARS) : void 0;
|
|
1851
|
+
const work = this.uploadAttachment(id, image3.input, { caption, logTs: ts, contentType: image3.contentType }).then(() => void 0).catch(() => void 0);
|
|
1813
1852
|
this.pendingAttachments.add(work);
|
|
1814
1853
|
void work.finally(() => this.pendingAttachments.delete(work));
|
|
1815
|
-
return
|
|
1854
|
+
return id;
|
|
1816
1855
|
}
|
|
1817
1856
|
/** Retorna o motivo da falha, ou undefined em sucesso. */
|
|
1818
|
-
async uploadAttachment(id,
|
|
1857
|
+
async uploadAttachment(id, image3, meta) {
|
|
1819
1858
|
let bytes;
|
|
1820
1859
|
try {
|
|
1821
|
-
bytes = await toBytes(
|
|
1860
|
+
bytes = await toBytes(image3);
|
|
1822
1861
|
} catch (error) {
|
|
1823
|
-
this.log("warning", `
|
|
1862
|
+
this.log("warning", `imagem n\xE3o p\xF4de ser lida: ${String(error)}`);
|
|
1824
1863
|
return "read-failed";
|
|
1825
1864
|
}
|
|
1826
1865
|
const contentType = sniffContentType(bytes) ?? meta.contentType;
|
|
1827
1866
|
if (!contentType) {
|
|
1828
|
-
this.log("warning", "
|
|
1867
|
+
this.log("warning", "imagem descartada: formato n\xE3o reconhecido (aceitos: PNG, JPEG, WEBP)");
|
|
1829
1868
|
return "unsupported-format";
|
|
1830
1869
|
}
|
|
1831
1870
|
if (bytes.byteLength > MAX_ATTACHMENT_BYTES) {
|
|
1832
1871
|
this.log(
|
|
1833
1872
|
"warning",
|
|
1834
|
-
`
|
|
1873
|
+
`imagem descartada: ${Math.round(bytes.byteLength / 1024)} KB excede o limite de ${MAX_ATTACHMENT_BYTES / 1024 / 1024} MB`
|
|
1835
1874
|
);
|
|
1836
1875
|
return "too-large";
|
|
1837
1876
|
}
|
|
@@ -1862,11 +1901,11 @@ var RunReporter = class {
|
|
|
1862
1901
|
return "unsupported";
|
|
1863
1902
|
}
|
|
1864
1903
|
if (error instanceof TasksApiError && error.status === 429) {
|
|
1865
|
-
this.log("warning", "
|
|
1904
|
+
this.log("warning", "imagem descartada: limite de anexos por execu\xE7\xE3o atingido no servidor");
|
|
1866
1905
|
return "quota";
|
|
1867
1906
|
}
|
|
1868
1907
|
if (this.abortController.signal.aborted) return "canceled";
|
|
1869
|
-
this.log("warning", `falha ao enviar
|
|
1908
|
+
this.log("warning", `falha ao enviar a imagem: ${String(error)}`);
|
|
1870
1909
|
void this.api.confirmAttachment(this.runId, id, { status: "failed" }).catch(() => {
|
|
1871
1910
|
});
|
|
1872
1911
|
return "upload-failed";
|
|
@@ -2030,23 +2069,39 @@ var Step = class {
|
|
|
2030
2069
|
/**
|
|
2031
2070
|
* Desfecho da step. `message` (opcional) é o motivo legível — vai em `data.message`
|
|
2032
2071
|
* e aparece na Central (performance e banner da execução) e no log de FINISH.
|
|
2072
|
+
* `options.image` pendura um print do estado da tela em `data.attachments`.
|
|
2033
2073
|
*/
|
|
2034
|
-
setStatus(status, message) {
|
|
2074
|
+
setStatus(status, message, options) {
|
|
2035
2075
|
this.status = status;
|
|
2036
2076
|
if (message) this.setData("message", stepMessage(message));
|
|
2077
|
+
if (options?.image !== void 0) this.attachImage(options.image);
|
|
2037
2078
|
return this;
|
|
2038
2079
|
}
|
|
2039
2080
|
/**
|
|
2040
2081
|
* Marca a step como `error` guardando o motivo em `data.error` ({ type, message }).
|
|
2041
2082
|
* Aceita qualquer valor lançado; `message` sobrescreve a mensagem do erro.
|
|
2083
|
+
* `options.image` pendura um print do estado da tela em `data.attachments`.
|
|
2042
2084
|
*/
|
|
2043
|
-
setError(error, message) {
|
|
2085
|
+
setError(error, message, options) {
|
|
2044
2086
|
this.status = "error";
|
|
2045
2087
|
const type = error instanceof Error ? error.name : typeof error === "object" && error !== null ? "Error" : void 0;
|
|
2046
2088
|
const raw = message ?? (error instanceof Error ? error.message || error.name : String(error));
|
|
2047
2089
|
this.setData("error", { ...type ? { type } : {}, message: stepMessage(raw) });
|
|
2090
|
+
if (options?.image !== void 0) this.attachImage(options.image);
|
|
2048
2091
|
return this;
|
|
2049
2092
|
}
|
|
2093
|
+
/**
|
|
2094
|
+
* Dispara o upload pela run (sink do RunReporter) e referencia o anexo em
|
|
2095
|
+
* `data.attachments` (chave reservada; teto igual ao das linhas de log). Fora de uma
|
|
2096
|
+
* run não há onde pendurar a imagem — descarta com aviso único.
|
|
2097
|
+
*/
|
|
2098
|
+
attachImage(image3) {
|
|
2099
|
+
const current = Array.isArray(this.data?.attachments) ? this.data.attachments : [];
|
|
2100
|
+
if (current.length >= MAX_ATTACHMENTS_PER_LOG_ENTRY) return;
|
|
2101
|
+
const id = this.transaction.attachImage(image3);
|
|
2102
|
+
if (!id) return;
|
|
2103
|
+
this.setData("attachments", [...current, id]);
|
|
2104
|
+
}
|
|
2050
2105
|
/** Motivo registrado via setStatus(…, message) ou setError — usado no log de FINISH. */
|
|
2051
2106
|
reason() {
|
|
2052
2107
|
const error = this.data?.error;
|
|
@@ -2107,6 +2162,8 @@ var Transaction = class {
|
|
|
2107
2162
|
tags = {};
|
|
2108
2163
|
finished = false;
|
|
2109
2164
|
lifecycleSink;
|
|
2165
|
+
attachmentSink;
|
|
2166
|
+
warnedNoAttachmentSink = false;
|
|
2110
2167
|
// instalado pelo RunReporter: abrir uma step é um ponto de parada cooperativo (lança RunCanceled)
|
|
2111
2168
|
stepGuard;
|
|
2112
2169
|
// eventos emitidos antes do sink existir (o START da transaction sai no construtor,
|
|
@@ -2137,6 +2194,19 @@ var Transaction = class {
|
|
|
2137
2194
|
this.lifecycleBacklog = [];
|
|
2138
2195
|
for (const [level, message] of backlog) sink(level, message);
|
|
2139
2196
|
}
|
|
2197
|
+
/** Conecta o pipeline de anexos da run — é o que dá imagem às steps. */
|
|
2198
|
+
setAttachmentSink(sink) {
|
|
2199
|
+
this.attachmentSink = sink;
|
|
2200
|
+
}
|
|
2201
|
+
/** Imagem de uma step → id do anexo pela run; sem run, descarta com aviso único. */
|
|
2202
|
+
attachImage(image3) {
|
|
2203
|
+
if (this.attachmentSink) return this.attachmentSink(image3, nowISO());
|
|
2204
|
+
if (!this.warnedNoAttachmentSink) {
|
|
2205
|
+
this.warnedNoAttachmentSink = true;
|
|
2206
|
+
console.warn("[hobots] imagem de step descartada \u2014 s\xF3 \xE9 suportada dentro de uma execu\xE7\xE3o de task");
|
|
2207
|
+
}
|
|
2208
|
+
return void 0;
|
|
2209
|
+
}
|
|
2140
2210
|
/**
|
|
2141
2211
|
* Conecta a checagem de cancelamento chamada antes de cada step nova — é assim que
|
|
2142
2212
|
* `hobots.transaction.startChild()` (e `step.startChild()`) param uma run cancelada.
|
|
@@ -2904,13 +2974,8 @@ function forwardAttachment(rt, attachment, message, type) {
|
|
|
2904
2974
|
rt.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o de task");
|
|
2905
2975
|
return;
|
|
2906
2976
|
}
|
|
2907
|
-
const withMeta = typeof attachment === "object" && "image" in attachment ? attachment :
|
|
2908
|
-
|
|
2909
|
-
message,
|
|
2910
|
-
level: toRunLogLevel(type),
|
|
2911
|
-
caption: withMeta.caption,
|
|
2912
|
-
contentType: withMeta.contentType
|
|
2913
|
-
});
|
|
2977
|
+
const withMeta = typeof attachment === "object" && !isImageAttachment(attachment) && "image" in attachment ? image(attachment.image, { caption: attachment.caption, contentType: attachment.contentType }) : attachment;
|
|
2978
|
+
reporter.log(toRunLogLevel(type), message, withMeta);
|
|
2914
2979
|
}
|
|
2915
2980
|
function captureException(error, hint) {
|
|
2916
2981
|
const rt = runtime;
|
|
@@ -2957,12 +3022,16 @@ function warnOutsideRun(member) {
|
|
|
2957
3022
|
else console.warn(`[hobots] ${message}`);
|
|
2958
3023
|
}
|
|
2959
3024
|
var FALLBACK_TASK = "hobots";
|
|
3025
|
+
var fallbackLogAt = (level) => (message, ...rest) => {
|
|
3026
|
+
const { text, image: dropped } = splitLogArgs(message, rest);
|
|
3027
|
+
printRunLog(FALLBACK_TASK, level, dropped ? `${text} [imagem descartada]` : text);
|
|
3028
|
+
};
|
|
2960
3029
|
var fallbackLog = {
|
|
2961
|
-
debug: (
|
|
2962
|
-
info: (
|
|
2963
|
-
warn: (
|
|
2964
|
-
error: (
|
|
2965
|
-
success: (
|
|
3030
|
+
debug: fallbackLogAt("debug"),
|
|
3031
|
+
info: fallbackLogAt("info"),
|
|
3032
|
+
warn: fallbackLogAt("warning"),
|
|
3033
|
+
error: fallbackLogAt("error"),
|
|
3034
|
+
success: fallbackLogAt("success")
|
|
2966
3035
|
};
|
|
2967
3036
|
var noop = () => void 0;
|
|
2968
3037
|
var fallbackEachSink = {
|
|
@@ -3008,9 +3077,6 @@ var hobots = Object.defineProperties({}, {
|
|
|
3008
3077
|
signal: getter("signal", () => neverAborts),
|
|
3009
3078
|
throwIfCanceled: method(() => resolve("throwIfCanceled")?.throwIfCanceled()),
|
|
3010
3079
|
sleep: method((ms) => resolve("sleep")?.sleep(ms) ?? sleep(ms)),
|
|
3011
|
-
attach: method(
|
|
3012
|
-
(image, options) => resolve("attach")?.attach(image, options) ?? Promise.resolve({ id: "", ok: false, reason: "canceled" })
|
|
3013
|
-
),
|
|
3014
3080
|
retry: method((fn, options) => {
|
|
3015
3081
|
const reporter = currentReporter();
|
|
3016
3082
|
return reporter ? reporter.context.retry(fn, options) : retryWithBackoff(fn, options);
|
|
@@ -3025,7 +3091,8 @@ var hobots = Object.defineProperties({}, {
|
|
|
3025
3091
|
installGlobalHobots();
|
|
3026
3092
|
}),
|
|
3027
3093
|
close: method((timeoutMs) => close(timeoutMs)),
|
|
3028
|
-
captureException: method((error, hint) => captureException(error, hint))
|
|
3094
|
+
captureException: method((error, hint) => captureException(error, hint)),
|
|
3095
|
+
image: method(image)
|
|
3029
3096
|
});
|
|
3030
3097
|
function installGlobalHobots() {
|
|
3031
3098
|
if (globalThis.hobots === hobots) return;
|
|
@@ -3036,6 +3103,7 @@ var start2 = hobots.start;
|
|
|
3036
3103
|
var close2 = hobots.close;
|
|
3037
3104
|
var captureException2 = hobots.captureException;
|
|
3038
3105
|
var retry = hobots.retry;
|
|
3106
|
+
var image2 = hobots.image;
|
|
3039
3107
|
|
|
3040
3108
|
// src/index.ts
|
|
3041
3109
|
var src_default = hobots;
|
|
@@ -3044,6 +3112,7 @@ export {
|
|
|
3044
3112
|
close2 as close,
|
|
3045
3113
|
src_default as default,
|
|
3046
3114
|
hobots,
|
|
3115
|
+
image2 as image,
|
|
3047
3116
|
register2 as register,
|
|
3048
3117
|
retry,
|
|
3049
3118
|
start2 as start
|