hobots 3.3.0 → 4.0.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 +38 -29
- package/dist/index.cjs +247 -290
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -60
- package/dist/index.d.ts +100 -60
- package/dist/index.js +247 -290
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +26 -24
- package/dist/protocol.d.ts +26 -24
- package/docs/README.md +10 -8
- package/docs/api-reference.md +37 -23
- package/docs/concepts.md +19 -19
- package/docs/getting-started.md +14 -12
- package/docs/protocol.md +31 -29
- package/docs/{tasks.md → runs.md} +45 -26
- package/package.json +4 -2
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 ? "
|
|
99
|
+
var SDK_VERSION = true ? "4.0.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
|
}
|
|
@@ -582,7 +580,7 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
582
580
|
const origin = fromOption ? "clientSecret" : `env ${CLIENT_SECRET_ENV}`;
|
|
583
581
|
if (!key) {
|
|
584
582
|
throw new Error(
|
|
585
|
-
`${prefix} Client Secret ausente \u2014 defina a env ${CLIENT_SECRET_ENV} (ou passe clientSecret nas op\xE7\xF5es
|
|
583
|
+
`${prefix} Client Secret ausente \u2014 defina a env ${CLIENT_SECRET_ENV} (ou passe clientSecret nas op\xE7\xF5es do register/start); copie o Client Secret do agente em Configura\xE7\xF5es \u2192 Agentes`
|
|
586
584
|
);
|
|
587
585
|
}
|
|
588
586
|
if (!SECRET_PATTERN.test(key)) {
|
|
@@ -595,7 +593,7 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
595
593
|
key,
|
|
596
594
|
baseUrl,
|
|
597
595
|
ingestEndpoint: `${baseUrl}/v1/ingest`,
|
|
598
|
-
|
|
596
|
+
runsBaseUrl: `${baseUrl}/v1/runs`
|
|
599
597
|
};
|
|
600
598
|
}
|
|
601
599
|
|
|
@@ -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),
|
|
@@ -958,7 +957,8 @@ var TasksApi = class {
|
|
|
958
957
|
const { signal: timeoutSignal, cleanup } = withTimeoutSignal(UPDATE_TIMEOUT_MS, signal);
|
|
959
958
|
let response;
|
|
960
959
|
try {
|
|
961
|
-
response = await fetch(`${this.baseUrl}
|
|
960
|
+
response = await fetch(`${this.baseUrl}`, {
|
|
961
|
+
redirect: "error",
|
|
962
962
|
method: "POST",
|
|
963
963
|
headers: this.headers(),
|
|
964
964
|
body: JSON.stringify(request),
|
|
@@ -971,12 +971,11 @@ var TasksApi = class {
|
|
|
971
971
|
throw new TasksApiError(`cria\xE7\xE3o da run falhou (HTTP ${response.status})`, response.status);
|
|
972
972
|
}
|
|
973
973
|
const body = await response.json().catch(() => null);
|
|
974
|
-
if (typeof body?.id !== "string"
|
|
975
|
-
throw new TasksApiError("resposta inv\xE1lida da cria\xE7\xE3o de run (id
|
|
974
|
+
if (typeof body?.id !== "string") {
|
|
975
|
+
throw new TasksApiError("resposta inv\xE1lida da cria\xE7\xE3o de run (id ausente)", 0);
|
|
976
976
|
}
|
|
977
977
|
return {
|
|
978
978
|
id: body.id,
|
|
979
|
-
task: body.task,
|
|
980
979
|
params: body.params && typeof body.params === "object" && !Array.isArray(body.params) ? body.params : {},
|
|
981
980
|
form: null,
|
|
982
981
|
source: typeof body.source === "string" ? body.source : "agent",
|
|
@@ -991,9 +990,9 @@ var TasksApi = class {
|
|
|
991
990
|
}
|
|
992
991
|
const valid = [];
|
|
993
992
|
for (const run of runs.slice(0, Math.max(0, max))) {
|
|
994
|
-
if (typeof run?.id !== "string"
|
|
993
|
+
if (typeof run?.id !== "string") continue;
|
|
995
994
|
const params = run.params && typeof run.params === "object" && !Array.isArray(run.params) ? run.params : {};
|
|
996
|
-
valid.push({ ...run, id: run.id,
|
|
995
|
+
valid.push({ ...run, id: run.id, params });
|
|
997
996
|
}
|
|
998
997
|
if (valid.length < runs.length) {
|
|
999
998
|
this.logger.warn(`poll retornou ${runs.length - valid.length} run(s) inv\xE1lida(s)/excedente(s) \u2014 descartadas`);
|
|
@@ -1008,7 +1007,8 @@ var TasksApi = class {
|
|
|
1008
1007
|
let response;
|
|
1009
1008
|
try {
|
|
1010
1009
|
response = await fetchWithRetry(
|
|
1011
|
-
() => fetch(`${this.baseUrl}
|
|
1010
|
+
() => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}`, {
|
|
1011
|
+
redirect: "error",
|
|
1012
1012
|
method: "PATCH",
|
|
1013
1013
|
headers: this.headers(),
|
|
1014
1014
|
body: JSON.stringify(update),
|
|
@@ -1029,7 +1029,8 @@ var TasksApi = class {
|
|
|
1029
1029
|
* que a run já foi cancelada/finalizada, o que é aceitável nesse caminho.
|
|
1030
1030
|
*/
|
|
1031
1031
|
async updateRunOnce(runId, update, timeoutMs = 2e3) {
|
|
1032
|
-
await fetch(`${this.baseUrl}
|
|
1032
|
+
await fetch(`${this.baseUrl}/${encodeURIComponent(runId)}`, {
|
|
1033
|
+
redirect: "error",
|
|
1033
1034
|
method: "PATCH",
|
|
1034
1035
|
headers: this.headers(),
|
|
1035
1036
|
body: JSON.stringify(update),
|
|
@@ -1045,7 +1046,8 @@ var TasksApi = class {
|
|
|
1045
1046
|
let response;
|
|
1046
1047
|
try {
|
|
1047
1048
|
response = await fetchWithRetry(
|
|
1048
|
-
() => fetch(`${this.baseUrl}
|
|
1049
|
+
() => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/items`, {
|
|
1050
|
+
redirect: "error",
|
|
1049
1051
|
method: "POST",
|
|
1050
1052
|
headers: this.headers(),
|
|
1051
1053
|
body: JSON.stringify(request),
|
|
@@ -1074,7 +1076,8 @@ var TasksApi = class {
|
|
|
1074
1076
|
() => withRequestTimeout(
|
|
1075
1077
|
UPDATE_TIMEOUT_MS,
|
|
1076
1078
|
signal,
|
|
1077
|
-
(timeoutSignal) => fetch(`${this.baseUrl}
|
|
1079
|
+
(timeoutSignal) => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/attachments`, {
|
|
1080
|
+
redirect: "error",
|
|
1078
1081
|
method: "POST",
|
|
1079
1082
|
headers: this.headers(),
|
|
1080
1083
|
body: JSON.stringify(request),
|
|
@@ -1107,6 +1110,7 @@ var TasksApi = class {
|
|
|
1107
1110
|
UPLOAD_TIMEOUT_MS,
|
|
1108
1111
|
signal,
|
|
1109
1112
|
(timeoutSignal) => fetch(url, {
|
|
1113
|
+
redirect: "error",
|
|
1110
1114
|
method: "PUT",
|
|
1111
1115
|
headers,
|
|
1112
1116
|
// BodyInit não aceita Uint8Array<ArrayBufferLike> em todos os lib.dom; o buffer é o mesmo
|
|
@@ -1123,8 +1127,9 @@ var TasksApi = class {
|
|
|
1123
1127
|
/** Marca o anexo como enviado (ou falho). Best-effort — a leitura não depende disso. */
|
|
1124
1128
|
async confirmAttachment(runId, attachmentId, request) {
|
|
1125
1129
|
const response = await fetch(
|
|
1126
|
-
`${this.baseUrl}
|
|
1130
|
+
`${this.baseUrl}/${encodeURIComponent(runId)}/attachments/${encodeURIComponent(attachmentId)}`,
|
|
1127
1131
|
{
|
|
1132
|
+
redirect: "error",
|
|
1128
1133
|
method: "PATCH",
|
|
1129
1134
|
headers: this.headers(),
|
|
1130
1135
|
body: JSON.stringify(request),
|
|
@@ -1207,22 +1212,22 @@ var ItemHandle = class {
|
|
|
1207
1212
|
sink;
|
|
1208
1213
|
reported = false;
|
|
1209
1214
|
status;
|
|
1210
|
-
succeeded(payload) {
|
|
1211
|
-
this.record("succeeded", void 0, payload);
|
|
1215
|
+
succeeded(payload, options) {
|
|
1216
|
+
this.record("succeeded", void 0, payload, options?.image);
|
|
1212
1217
|
}
|
|
1213
|
-
failed(message, payload) {
|
|
1214
|
-
this.record("failed", message, payload);
|
|
1218
|
+
failed(message, payload, options) {
|
|
1219
|
+
this.record("failed", message, payload, options?.image);
|
|
1215
1220
|
if (this.step && this.step.status !== "error") this.step.setStatus("error", message);
|
|
1216
1221
|
}
|
|
1217
|
-
occurrence(message, payload) {
|
|
1218
|
-
this.record("occurrence", message, payload);
|
|
1222
|
+
occurrence(message, payload, options) {
|
|
1223
|
+
this.record("occurrence", message, payload, options?.image);
|
|
1219
1224
|
if (this.step && this.step.status === "ok") this.step.setStatus("warning", message);
|
|
1220
1225
|
}
|
|
1221
|
-
record(status, message, payload) {
|
|
1226
|
+
record(status, message, payload, image3) {
|
|
1222
1227
|
this.reported = true;
|
|
1223
1228
|
this.status = status;
|
|
1224
1229
|
const merged = { ...this.basePayload ?? {}, ...payload ?? {} };
|
|
1225
|
-
this.sink.report(status, message, Object.keys(merged).length > 0 ? merged : void 0, this.id);
|
|
1230
|
+
this.sink.report(status, message, Object.keys(merged).length > 0 ? merged : void 0, this.id, image3);
|
|
1226
1231
|
}
|
|
1227
1232
|
};
|
|
1228
1233
|
function resolveId(options, element, index) {
|
|
@@ -1302,6 +1307,27 @@ var MAX_ATTACHMENTS_PER_LOG_ENTRY = WIRE_LIMITS.MAX_ATTACHMENTS_PER_LOG_ENTRY;
|
|
|
1302
1307
|
var MAX_CONCURRENT_UPLOADS = 3;
|
|
1303
1308
|
var MAX_CAPTION_CHARS = WIRE_LIMITS.MAX_CAPTION_CHARS;
|
|
1304
1309
|
var ATTACHMENT_DRAIN_MS = 15e3;
|
|
1310
|
+
var IMAGE_BRAND = /* @__PURE__ */ Symbol.for("hobots.image");
|
|
1311
|
+
var ImageAttachment = class {
|
|
1312
|
+
constructor(input, caption, contentType) {
|
|
1313
|
+
this.input = input;
|
|
1314
|
+
this.caption = caption;
|
|
1315
|
+
this.contentType = contentType;
|
|
1316
|
+
}
|
|
1317
|
+
input;
|
|
1318
|
+
caption;
|
|
1319
|
+
contentType;
|
|
1320
|
+
[IMAGE_BRAND] = true;
|
|
1321
|
+
};
|
|
1322
|
+
function image(input, options = {}) {
|
|
1323
|
+
return new ImageAttachment(input, options.caption, options.contentType);
|
|
1324
|
+
}
|
|
1325
|
+
function isImageAttachment(value) {
|
|
1326
|
+
return typeof value === "object" && value !== null && value[IMAGE_BRAND] === true;
|
|
1327
|
+
}
|
|
1328
|
+
function toImageAttachment(value) {
|
|
1329
|
+
return isImageAttachment(value) ? value : new ImageAttachment(value);
|
|
1330
|
+
}
|
|
1305
1331
|
async function readImageFile(path) {
|
|
1306
1332
|
if (!isNode()) {
|
|
1307
1333
|
throw new Error("caminho de arquivo s\xF3 \xE9 suportado em Node");
|
|
@@ -1351,8 +1377,16 @@ function joinLogArgs(message, rest) {
|
|
|
1351
1377
|
if (rest.length === 0) return safeString(message);
|
|
1352
1378
|
return [message, ...rest].map(safeString).join(" ");
|
|
1353
1379
|
}
|
|
1354
|
-
function
|
|
1355
|
-
const
|
|
1380
|
+
function splitLogArgs(message, rest) {
|
|
1381
|
+
const args = [message, ...rest];
|
|
1382
|
+
const last = args[args.length - 1];
|
|
1383
|
+
if (!isImageAttachment(last)) return { text: joinLogArgs(message, rest) };
|
|
1384
|
+
const text = args.slice(0, -1);
|
|
1385
|
+
if (text.length === 0) return { text: last.caption !== void 0 ? safeString(last.caption) : "captura de tela", image: last };
|
|
1386
|
+
return { text: joinLogArgs(text[0], text.slice(1)), image: last };
|
|
1387
|
+
}
|
|
1388
|
+
function printRunLog(level, message) {
|
|
1389
|
+
const line = colorize(`[hobots] ${level.toUpperCase()}`, LOG_LEVEL_COLOR[level]);
|
|
1356
1390
|
const method2 = level === "warning" ? "warn" : level === "success" ? "info" : level;
|
|
1357
1391
|
console[method2](line, message);
|
|
1358
1392
|
}
|
|
@@ -1398,7 +1432,7 @@ function scrubPayloadValue(value, depth) {
|
|
|
1398
1432
|
return out;
|
|
1399
1433
|
}
|
|
1400
1434
|
var RunReporter = class {
|
|
1401
|
-
constructor(api, runId,
|
|
1435
|
+
constructor(api, runId, logger, tx, flushIntervalMs, cancelCheckIntervalMs, params = {}) {
|
|
1402
1436
|
this.api = api;
|
|
1403
1437
|
this.runId = runId;
|
|
1404
1438
|
this.logger = logger;
|
|
@@ -1407,18 +1441,23 @@ var RunReporter = class {
|
|
|
1407
1441
|
MAX_CANCEL_CHECK_INTERVAL_MS,
|
|
1408
1442
|
Math.max(MIN_CANCEL_CHECK_INTERVAL_MS, cancelCheckIntervalMs ?? DEFAULT_CANCEL_CHECK_INTERVAL_MS)
|
|
1409
1443
|
);
|
|
1444
|
+
const logAt = (level) => (message, ...rest) => {
|
|
1445
|
+
const { text, image: image3 } = splitLogArgs(message, rest);
|
|
1446
|
+
this.log(level, text, image3);
|
|
1447
|
+
};
|
|
1410
1448
|
const log = {
|
|
1411
|
-
debug: (
|
|
1412
|
-
info: (
|
|
1413
|
-
warn: (
|
|
1414
|
-
error: (
|
|
1415
|
-
success: (
|
|
1449
|
+
debug: logAt("debug"),
|
|
1450
|
+
info: logAt("info"),
|
|
1451
|
+
warn: logAt("warning"),
|
|
1452
|
+
error: logAt("error"),
|
|
1453
|
+
success: logAt("success")
|
|
1416
1454
|
};
|
|
1417
1455
|
tx.setLifecycleSink((level, message) => this.push(level, message));
|
|
1456
|
+
tx.setAttachmentSink((image3, ts) => this.queueAttachment(image3, ts));
|
|
1418
1457
|
const checkCanceled = () => this.checkCanceled();
|
|
1419
1458
|
tx.setStepGuard(checkCanceled);
|
|
1420
1459
|
const eachSink = {
|
|
1421
|
-
report: (status, message, payload, id) => this.addItem(status, message, payload, id),
|
|
1460
|
+
report: (status, message, payload, id, image3) => this.addItem(status, message, payload, id, image3),
|
|
1422
1461
|
startStep: (op, description) => tx.startChild(op, description),
|
|
1423
1462
|
declareTotal: (total) => {
|
|
1424
1463
|
if (this.progressTotal == null) this.setProgress(this.progressCurrent, total);
|
|
@@ -1431,19 +1470,19 @@ var RunReporter = class {
|
|
|
1431
1470
|
const items = {
|
|
1432
1471
|
add: (item) => {
|
|
1433
1472
|
checkCanceled();
|
|
1434
|
-
this.addItem(item.status, item.message, item.payload, item.id);
|
|
1473
|
+
this.addItem(item.status, item.message, item.payload, item.id, item.image);
|
|
1435
1474
|
},
|
|
1436
1475
|
succeeded: (payload, options) => {
|
|
1437
1476
|
checkCanceled();
|
|
1438
|
-
this.addItem("succeeded", void 0, payload, options?.id);
|
|
1477
|
+
this.addItem("succeeded", void 0, payload, options?.id, options?.image);
|
|
1439
1478
|
},
|
|
1440
1479
|
failed: (message, payload, options) => {
|
|
1441
1480
|
checkCanceled();
|
|
1442
|
-
this.addItem("failed", message, payload, options?.id);
|
|
1481
|
+
this.addItem("failed", message, payload, options?.id, options?.image);
|
|
1443
1482
|
},
|
|
1444
1483
|
occurrence: (message, payload, options) => {
|
|
1445
1484
|
checkCanceled();
|
|
1446
|
-
this.addItem("occurrence", message, payload, options?.id);
|
|
1485
|
+
this.addItem("occurrence", message, payload, options?.id, options?.image);
|
|
1447
1486
|
},
|
|
1448
1487
|
each: (list, handler, options = {}) => eachItem(list, handler, options, eachSink)
|
|
1449
1488
|
};
|
|
@@ -1475,7 +1514,6 @@ var RunReporter = class {
|
|
|
1475
1514
|
const sleep2 = (ms) => cancelableSleep(ms, signal);
|
|
1476
1515
|
this.context = {
|
|
1477
1516
|
runId,
|
|
1478
|
-
task,
|
|
1479
1517
|
params,
|
|
1480
1518
|
log,
|
|
1481
1519
|
items,
|
|
@@ -1484,7 +1522,6 @@ var RunReporter = class {
|
|
|
1484
1522
|
signal,
|
|
1485
1523
|
throwIfCanceled: checkCanceled,
|
|
1486
1524
|
sleep: sleep2,
|
|
1487
|
-
attach: (image, options) => this.attach(image, options),
|
|
1488
1525
|
retry: (fn, options) => retryWithBackoff(fn, { ...options, signal, wait: sleep2 })
|
|
1489
1526
|
};
|
|
1490
1527
|
}
|
|
@@ -1575,15 +1612,21 @@ var RunReporter = class {
|
|
|
1575
1612
|
this.clearFlushTimer();
|
|
1576
1613
|
return this.takeLogs().slice(-MAX_LOGS_PER_PATCH);
|
|
1577
1614
|
}
|
|
1578
|
-
|
|
1615
|
+
/**
|
|
1616
|
+
* Linha de log da run com eco no terminal. `image` (opcional) vira o anexo da linha:
|
|
1617
|
+
* o id entra na entrada ANTES de qualquer I/O — é isso que mantém a ordem dos logs
|
|
1618
|
+
* intacta e o handler livre da latência do upload.
|
|
1619
|
+
*/
|
|
1620
|
+
log(level, message, image3) {
|
|
1579
1621
|
if (this.dead) return;
|
|
1580
1622
|
const scrubbed = scrubSecrets(safeString(message));
|
|
1581
|
-
|
|
1582
|
-
this.
|
|
1623
|
+
const ts = nowISO();
|
|
1624
|
+
const attachmentId = image3 === void 0 ? void 0 : this.queueAttachment(image3, ts);
|
|
1625
|
+
printRunLog(level, attachmentId ? `${scrubbed} [imagem]` : scrubbed);
|
|
1626
|
+
this.push(level, scrubbed, true, attachmentId ? [attachmentId] : void 0, ts);
|
|
1583
1627
|
}
|
|
1584
1628
|
/** Bufferiza uma entrada de log sem eco no terminal (cap, truncate, flush). */
|
|
1585
|
-
push(level, message, alreadyScrubbed = false, attachments) {
|
|
1586
|
-
const ts = nowISO();
|
|
1629
|
+
push(level, message, alreadyScrubbed = false, attachments, ts = nowISO()) {
|
|
1587
1630
|
if (this.dead) return ts;
|
|
1588
1631
|
const scrubbed = alreadyScrubbed ? message : scrubSecrets(message);
|
|
1589
1632
|
this.buffer.push({
|
|
@@ -1733,7 +1776,7 @@ var RunReporter = class {
|
|
|
1733
1776
|
* de status, o servidor aceita itens mesmo com a run cancelada (trabalho já feito),
|
|
1734
1777
|
* então um reporter morto não descarta: só um kill() de exit path descarta.
|
|
1735
1778
|
*/
|
|
1736
|
-
addItem(status, message, payload, id) {
|
|
1779
|
+
addItem(status, message, payload, id, image3) {
|
|
1737
1780
|
if (this.itemsClosed || this.itemsUnsupported) return;
|
|
1738
1781
|
if (this.itemCount >= MAX_ITEMS_PER_RUN) {
|
|
1739
1782
|
if (!this.itemsQuotaExceeded) {
|
|
@@ -1758,13 +1801,16 @@ var RunReporter = class {
|
|
|
1758
1801
|
scrubbedPayload = void 0;
|
|
1759
1802
|
}
|
|
1760
1803
|
}
|
|
1804
|
+
const ts = nowISO();
|
|
1805
|
+
const attachmentId = image3 === void 0 ? void 0 : this.queueAttachment(image3, ts);
|
|
1761
1806
|
this.itemsBuffer.push({
|
|
1762
1807
|
id: id ? truncate(String(id), 255) : uuid4(),
|
|
1763
1808
|
seq: this.itemSeq++,
|
|
1764
1809
|
status,
|
|
1765
1810
|
...message ? { message: truncate(scrubSecrets(safeString(message)), MAX_ITEM_MESSAGE_CHARS) } : {},
|
|
1766
1811
|
...scrubbedPayload ? { payload: scrubbedPayload } : {},
|
|
1767
|
-
ts
|
|
1812
|
+
ts,
|
|
1813
|
+
...attachmentId ? { attachments: [attachmentId] } : {}
|
|
1768
1814
|
});
|
|
1769
1815
|
if (this.itemsBuffer.length >= ITEMS_FLUSH_AT_BUFFERED) {
|
|
1770
1816
|
this.flushItems();
|
|
@@ -1844,59 +1890,51 @@ var RunReporter = class {
|
|
|
1844
1890
|
}
|
|
1845
1891
|
// ── Anexos ───────────────────────────────────────────────────────────────────
|
|
1846
1892
|
/**
|
|
1847
|
-
*
|
|
1848
|
-
*
|
|
1849
|
-
*
|
|
1850
|
-
*
|
|
1893
|
+
* Pendura uma imagem num evento da run (log, item ou step): faz as checagens que não
|
|
1894
|
+
* dependem de I/O (reporter morto, instalação sem anexos, cota), reserva o id e dispara
|
|
1895
|
+
* o upload em segundo plano. Devolve o id — já referenciável pelo evento — ou undefined
|
|
1896
|
+
* quando a imagem foi descartada (sempre com aviso no log da run). Nunca lança: todo
|
|
1897
|
+
* caminho de erro do upload vira aviso. O hint `attachment` de `hobots.captureException`
|
|
1898
|
+
* também cai aqui via `log()`.
|
|
1851
1899
|
*/
|
|
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
|
-
}
|
|
1900
|
+
queueAttachment(imageIn, ts) {
|
|
1901
|
+
if (this.dead) return void 0;
|
|
1902
|
+
const image3 = toImageAttachment(imageIn);
|
|
1903
|
+
if (this.attachmentsUnsupported) return void 0;
|
|
1864
1904
|
if (this.attachmentCount >= MAX_ATTACHMENTS_PER_RUN) {
|
|
1865
|
-
this.
|
|
1866
|
-
|
|
1867
|
-
|
|
1905
|
+
if (this.attachmentCount === MAX_ATTACHMENTS_PER_RUN) {
|
|
1906
|
+
this.attachmentCount++;
|
|
1907
|
+
this.log("warning", `imagem descartada: limite de ${MAX_ATTACHMENTS_PER_RUN} por execu\xE7\xE3o atingido`);
|
|
1908
|
+
}
|
|
1909
|
+
return void 0;
|
|
1868
1910
|
}
|
|
1869
1911
|
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" }));
|
|
1912
|
+
const id = uuid4();
|
|
1913
|
+
const rawCaption = image3.caption !== void 0 ? safeString(image3.caption) : void 0;
|
|
1914
|
+
const caption = rawCaption ? truncate(scrubSecrets(rawCaption), MAX_CAPTION_CHARS) : void 0;
|
|
1915
|
+
const work = this.uploadAttachment(id, image3.input, { caption, logTs: ts, contentType: image3.contentType }).then(() => void 0).catch(() => void 0);
|
|
1878
1916
|
this.pendingAttachments.add(work);
|
|
1879
1917
|
void work.finally(() => this.pendingAttachments.delete(work));
|
|
1880
|
-
return
|
|
1918
|
+
return id;
|
|
1881
1919
|
}
|
|
1882
1920
|
/** Retorna o motivo da falha, ou undefined em sucesso. */
|
|
1883
|
-
async uploadAttachment(id,
|
|
1921
|
+
async uploadAttachment(id, image3, meta) {
|
|
1884
1922
|
let bytes;
|
|
1885
1923
|
try {
|
|
1886
|
-
bytes = await toBytes(
|
|
1924
|
+
bytes = await toBytes(image3);
|
|
1887
1925
|
} catch (error) {
|
|
1888
|
-
this.log("warning", `
|
|
1926
|
+
this.log("warning", `imagem n\xE3o p\xF4de ser lida: ${String(error)}`);
|
|
1889
1927
|
return "read-failed";
|
|
1890
1928
|
}
|
|
1891
1929
|
const contentType = sniffContentType(bytes) ?? meta.contentType;
|
|
1892
1930
|
if (!contentType) {
|
|
1893
|
-
this.log("warning", "
|
|
1931
|
+
this.log("warning", "imagem descartada: formato n\xE3o reconhecido (aceitos: PNG, JPEG, WEBP)");
|
|
1894
1932
|
return "unsupported-format";
|
|
1895
1933
|
}
|
|
1896
1934
|
if (bytes.byteLength > MAX_ATTACHMENT_BYTES) {
|
|
1897
1935
|
this.log(
|
|
1898
1936
|
"warning",
|
|
1899
|
-
`
|
|
1937
|
+
`imagem descartada: ${Math.round(bytes.byteLength / 1024)} KB excede o limite de ${MAX_ATTACHMENT_BYTES / 1024 / 1024} MB`
|
|
1900
1938
|
);
|
|
1901
1939
|
return "too-large";
|
|
1902
1940
|
}
|
|
@@ -1927,11 +1965,11 @@ var RunReporter = class {
|
|
|
1927
1965
|
return "unsupported";
|
|
1928
1966
|
}
|
|
1929
1967
|
if (error instanceof TasksApiError && error.status === 429) {
|
|
1930
|
-
this.log("warning", "
|
|
1968
|
+
this.log("warning", "imagem descartada: limite de anexos por execu\xE7\xE3o atingido no servidor");
|
|
1931
1969
|
return "quota";
|
|
1932
1970
|
}
|
|
1933
1971
|
if (this.abortController.signal.aborted) return "canceled";
|
|
1934
|
-
this.log("warning", `falha ao enviar
|
|
1972
|
+
this.log("warning", `falha ao enviar a imagem: ${String(error)}`);
|
|
1935
1973
|
void this.api.confirmAttachment(this.runId, id, { status: "failed" }).catch(() => {
|
|
1936
1974
|
});
|
|
1937
1975
|
return "upload-failed";
|
|
@@ -2095,23 +2133,39 @@ var Step = class {
|
|
|
2095
2133
|
/**
|
|
2096
2134
|
* Desfecho da step. `message` (opcional) é o motivo legível — vai em `data.message`
|
|
2097
2135
|
* e aparece na Central (performance e banner da execução) e no log de FINISH.
|
|
2136
|
+
* `options.image` pendura um print do estado da tela em `data.attachments`.
|
|
2098
2137
|
*/
|
|
2099
|
-
setStatus(status, message) {
|
|
2138
|
+
setStatus(status, message, options) {
|
|
2100
2139
|
this.status = status;
|
|
2101
2140
|
if (message) this.setData("message", stepMessage(message));
|
|
2141
|
+
if (options?.image !== void 0) this.attachImage(options.image);
|
|
2102
2142
|
return this;
|
|
2103
2143
|
}
|
|
2104
2144
|
/**
|
|
2105
2145
|
* Marca a step como `error` guardando o motivo em `data.error` ({ type, message }).
|
|
2106
2146
|
* Aceita qualquer valor lançado; `message` sobrescreve a mensagem do erro.
|
|
2147
|
+
* `options.image` pendura um print do estado da tela em `data.attachments`.
|
|
2107
2148
|
*/
|
|
2108
|
-
setError(error, message) {
|
|
2149
|
+
setError(error, message, options) {
|
|
2109
2150
|
this.status = "error";
|
|
2110
2151
|
const type = error instanceof Error ? error.name : typeof error === "object" && error !== null ? "Error" : void 0;
|
|
2111
2152
|
const raw = message ?? (error instanceof Error ? error.message || error.name : String(error));
|
|
2112
2153
|
this.setData("error", { ...type ? { type } : {}, message: stepMessage(raw) });
|
|
2154
|
+
if (options?.image !== void 0) this.attachImage(options.image);
|
|
2113
2155
|
return this;
|
|
2114
2156
|
}
|
|
2157
|
+
/**
|
|
2158
|
+
* Dispara o upload pela run (sink do RunReporter) e referencia o anexo em
|
|
2159
|
+
* `data.attachments` (chave reservada; teto igual ao das linhas de log). Fora de uma
|
|
2160
|
+
* run não há onde pendurar a imagem — descarta com aviso único.
|
|
2161
|
+
*/
|
|
2162
|
+
attachImage(image3) {
|
|
2163
|
+
const current = Array.isArray(this.data?.attachments) ? this.data.attachments : [];
|
|
2164
|
+
if (current.length >= MAX_ATTACHMENTS_PER_LOG_ENTRY) return;
|
|
2165
|
+
const id = this.transaction.attachImage(image3);
|
|
2166
|
+
if (!id) return;
|
|
2167
|
+
this.setData("attachments", [...current, id]);
|
|
2168
|
+
}
|
|
2115
2169
|
/** Motivo registrado via setStatus(…, message) ou setError — usado no log de FINISH. */
|
|
2116
2170
|
reason() {
|
|
2117
2171
|
const error = this.data?.error;
|
|
@@ -2172,6 +2226,8 @@ var Transaction = class {
|
|
|
2172
2226
|
tags = {};
|
|
2173
2227
|
finished = false;
|
|
2174
2228
|
lifecycleSink;
|
|
2229
|
+
attachmentSink;
|
|
2230
|
+
warnedNoAttachmentSink = false;
|
|
2175
2231
|
// instalado pelo RunReporter: abrir uma step é um ponto de parada cooperativo (lança RunCanceled)
|
|
2176
2232
|
stepGuard;
|
|
2177
2233
|
// eventos emitidos antes do sink existir (o START da transaction sai no construtor,
|
|
@@ -2202,6 +2258,19 @@ var Transaction = class {
|
|
|
2202
2258
|
this.lifecycleBacklog = [];
|
|
2203
2259
|
for (const [level, message] of backlog) sink(level, message);
|
|
2204
2260
|
}
|
|
2261
|
+
/** Conecta o pipeline de anexos da run — é o que dá imagem às steps. */
|
|
2262
|
+
setAttachmentSink(sink) {
|
|
2263
|
+
this.attachmentSink = sink;
|
|
2264
|
+
}
|
|
2265
|
+
/** Imagem de uma step → id do anexo pela run; sem run, descarta com aviso único. */
|
|
2266
|
+
attachImage(image3) {
|
|
2267
|
+
if (this.attachmentSink) return this.attachmentSink(image3, nowISO());
|
|
2268
|
+
if (!this.warnedNoAttachmentSink) {
|
|
2269
|
+
this.warnedNoAttachmentSink = true;
|
|
2270
|
+
console.warn("[hobots] imagem de step descartada \u2014 s\xF3 \xE9 suportada dentro de uma execu\xE7\xE3o");
|
|
2271
|
+
}
|
|
2272
|
+
return void 0;
|
|
2273
|
+
}
|
|
2205
2274
|
/**
|
|
2206
2275
|
* Conecta a checagem de cancelamento chamada antes de cada step nova — é assim que
|
|
2207
2276
|
* `hobots.transaction.startChild()` (e `step.startChild()`) param uma run cancelada.
|
|
@@ -2264,13 +2333,12 @@ var Transaction = class {
|
|
|
2264
2333
|
// src/tasks/runner.ts
|
|
2265
2334
|
var DEFAULT_RETRY_DELAY_MS = 5e3;
|
|
2266
2335
|
async function executeRun(input) {
|
|
2267
|
-
const { run,
|
|
2268
|
-
const options =
|
|
2269
|
-
const tx = new Transaction(client,
|
|
2336
|
+
const { run, handler, api, client, logger, tracker } = input;
|
|
2337
|
+
const options = handler.options;
|
|
2338
|
+
const tx = new Transaction(client, "run", "hobots.run", run.id);
|
|
2270
2339
|
const reporter = new RunReporter(
|
|
2271
2340
|
api,
|
|
2272
2341
|
run.id,
|
|
2273
|
-
run.task,
|
|
2274
2342
|
logger,
|
|
2275
2343
|
tx,
|
|
2276
2344
|
options.logFlushIntervalMs,
|
|
@@ -2286,9 +2354,9 @@ async function executeRun(input) {
|
|
|
2286
2354
|
tx.finish();
|
|
2287
2355
|
return;
|
|
2288
2356
|
}
|
|
2289
|
-
logger.debug(`executando run ${run.id}
|
|
2357
|
+
logger.debug(`executando run ${run.id}`);
|
|
2290
2358
|
try {
|
|
2291
|
-
await runWithRunContext({ reporter }, () => invokeWithRetry(
|
|
2359
|
+
await runWithRunContext({ reporter }, () => invokeWithRetry(handler, run, reporter, logger));
|
|
2292
2360
|
await reporter.finalizeAttachments();
|
|
2293
2361
|
await reporter.finalizeItems();
|
|
2294
2362
|
warnIncompleteProgress(reporter);
|
|
@@ -2316,11 +2384,11 @@ function warnIncompleteProgress(reporter) {
|
|
|
2316
2384
|
`execu\xE7\xE3o conclu\xEDda com progresso incompleto: ${snapshot.current}/${snapshot.total} \u2014 o handler terminou sem processar (ou sem avan\xE7ar) todos os itens previstos`
|
|
2317
2385
|
);
|
|
2318
2386
|
}
|
|
2319
|
-
function invokeWithRetry(
|
|
2320
|
-
const { retries, retryDelayMs } =
|
|
2387
|
+
function invokeWithRetry(agent, run, reporter, logger) {
|
|
2388
|
+
const { retries, retryDelayMs } = agent.options;
|
|
2321
2389
|
const total = retries + 1;
|
|
2322
2390
|
const ctx = reporter.context;
|
|
2323
|
-
return retryWithBackoff(() => invokeWithTimeout(
|
|
2391
|
+
return retryWithBackoff(() => invokeWithTimeout(agent, run, reporter, logger), {
|
|
2324
2392
|
retries,
|
|
2325
2393
|
delayMs: (attempt) => resolveDelay(retryDelayMs, attempt, DEFAULT_RETRY_DELAY_MS),
|
|
2326
2394
|
shouldRetry: () => !reporter.isDead,
|
|
@@ -2334,9 +2402,9 @@ function invokeWithRetry(task, run, reporter, logger) {
|
|
|
2334
2402
|
}
|
|
2335
2403
|
var TIMEOUT_SENTINEL = /* @__PURE__ */ Symbol("task-timeout");
|
|
2336
2404
|
var CANCEL_SENTINEL = /* @__PURE__ */ Symbol("task-canceled");
|
|
2337
|
-
async function invokeWithTimeout(
|
|
2338
|
-
const { timeoutMs, cancelGraceMs } =
|
|
2339
|
-
const handler =
|
|
2405
|
+
async function invokeWithTimeout(agent, run, reporter, logger) {
|
|
2406
|
+
const { timeoutMs, cancelGraceMs } = agent.options;
|
|
2407
|
+
const handler = agent.handler;
|
|
2340
2408
|
const invocation = Promise.resolve().then(() => handler(run.params, reporter.context));
|
|
2341
2409
|
const races = [];
|
|
2342
2410
|
if (timeoutMs && timeoutMs > 0) {
|
|
@@ -2393,7 +2461,7 @@ var TaskAgent = class {
|
|
|
2393
2461
|
this.logger = options.logger;
|
|
2394
2462
|
this.api = options.api;
|
|
2395
2463
|
this.client = options.client;
|
|
2396
|
-
this.
|
|
2464
|
+
this.handler = options.handler;
|
|
2397
2465
|
this.tracker = options.tracker;
|
|
2398
2466
|
this.pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
|
|
2399
2467
|
this.backoff = new Backoff(this.logger, "poll", this.pollIntervalMs);
|
|
@@ -2403,7 +2471,7 @@ var TaskAgent = class {
|
|
|
2403
2471
|
pollIntervalMs;
|
|
2404
2472
|
api;
|
|
2405
2473
|
client;
|
|
2406
|
-
|
|
2474
|
+
handler;
|
|
2407
2475
|
tracker;
|
|
2408
2476
|
backoff;
|
|
2409
2477
|
state = "idle";
|
|
@@ -2419,10 +2487,10 @@ var TaskAgent = class {
|
|
|
2419
2487
|
throw new Error("[hobots] start do agente durante o stop \u2014 aguarde o stop terminar");
|
|
2420
2488
|
}
|
|
2421
2489
|
this.state = "started";
|
|
2422
|
-
this.logger.debug(`polling iniciado (
|
|
2490
|
+
this.logger.debug(`polling iniciado (concorr\xEAncia ${this.handler.options.concurrency})`);
|
|
2423
2491
|
this.schedule(0);
|
|
2424
2492
|
}
|
|
2425
|
-
/**
|
|
2493
|
+
/** Antecipa o próximo poll para já. */
|
|
2426
2494
|
wake() {
|
|
2427
2495
|
if (this.state !== "started") return;
|
|
2428
2496
|
if (this.timer) {
|
|
@@ -2453,21 +2521,14 @@ var TaskAgent = class {
|
|
|
2453
2521
|
void this.tick();
|
|
2454
2522
|
}, delayMs);
|
|
2455
2523
|
}
|
|
2456
|
-
/** Slots livres
|
|
2524
|
+
/** Slots livres do agente — vira o `max` do poll (nunca reivindica trabalho para deixar parado). */
|
|
2457
2525
|
freeSlots() {
|
|
2458
|
-
const
|
|
2459
|
-
|
|
2460
|
-
for (const task of this.registry.remoteTasks()) {
|
|
2461
|
-
const free = task.options.concurrency - this.tracker.activeFor(task.slug);
|
|
2462
|
-
if (free <= 0) continue;
|
|
2463
|
-
tasks.push(task.slug);
|
|
2464
|
-
max += free;
|
|
2465
|
-
}
|
|
2466
|
-
return { tasks, max: Math.min(MAX_CLAIM_PER_POLL, max) };
|
|
2526
|
+
const free = this.handler.options.concurrency - this.tracker.activeCount;
|
|
2527
|
+
return Math.min(MAX_CLAIM_PER_POLL, Math.max(0, free));
|
|
2467
2528
|
}
|
|
2468
2529
|
async tick() {
|
|
2469
2530
|
if (this.state !== "started") return;
|
|
2470
|
-
const
|
|
2531
|
+
const max = this.freeSlots();
|
|
2471
2532
|
if (max === 0) {
|
|
2472
2533
|
this.schedule(this.pollIntervalMs);
|
|
2473
2534
|
return;
|
|
@@ -2477,11 +2538,9 @@ var TaskAgent = class {
|
|
|
2477
2538
|
const response = await this.api.poll(
|
|
2478
2539
|
{
|
|
2479
2540
|
// identidade canônica do agente no painel — o mesmo instanceId do heartbeat,
|
|
2480
|
-
// então os dois canais convergem em um agente
|
|
2481
|
-
// tasks (register + start); o filtro `tasks` abaixo só as que este poll pode rodar.
|
|
2541
|
+
// então os dois canais convergem em um agente
|
|
2482
2542
|
instance: this.options.instanceInfo(),
|
|
2483
|
-
max
|
|
2484
|
-
tasks
|
|
2543
|
+
max
|
|
2485
2544
|
},
|
|
2486
2545
|
this.pollAbort.signal
|
|
2487
2546
|
);
|
|
@@ -2505,17 +2564,7 @@ var TaskAgent = class {
|
|
|
2505
2564
|
this.activeRuns.add(promise);
|
|
2506
2565
|
}
|
|
2507
2566
|
async executeClaimed(run) {
|
|
2508
|
-
|
|
2509
|
-
if (!task || task.mode !== "remote") {
|
|
2510
|
-
const tx = new Transaction(this.client, `run:${run.task}`, "hobots.task", run.id);
|
|
2511
|
-
const reporter = new RunReporter(this.api, run.id, run.task, this.logger, tx);
|
|
2512
|
-
reporter.reportFailure(toRunError(Object.assign(new Error(`tarefa n\xE3o registrada no agente: ${run.task}`), { name: "HandlerNotFound" })));
|
|
2513
|
-
await reporter.finalize();
|
|
2514
|
-
tx.setStatus("error");
|
|
2515
|
-
tx.finish();
|
|
2516
|
-
return;
|
|
2517
|
-
}
|
|
2518
|
-
await executeRun({ run, task, api: this.api, client: this.client, logger: this.logger, tracker: this.tracker });
|
|
2567
|
+
await executeRun({ run, handler: this.handler, api: this.api, client: this.client, logger: this.logger, tracker: this.tracker });
|
|
2519
2568
|
}
|
|
2520
2569
|
};
|
|
2521
2570
|
|
|
@@ -2523,11 +2572,11 @@ var TaskAgent = class {
|
|
|
2523
2572
|
var KEY_RE = /^[a-zA-Z0-9_.-]{1,100}$/;
|
|
2524
2573
|
var MAX_COLUMNS = WIRE_LIMITS.MAX_ITEM_COLUMNS;
|
|
2525
2574
|
var MAX_LABEL_CHARS = WIRE_LIMITS.MAX_ITEM_COLUMN_LABEL_CHARS;
|
|
2526
|
-
function normalizeItemColumns(
|
|
2575
|
+
function normalizeItemColumns(input, logger) {
|
|
2527
2576
|
if (input === void 0 || input === null) return void 0;
|
|
2528
2577
|
const columns = input.columns;
|
|
2529
2578
|
if (typeof input !== "object" || !Array.isArray(columns)) {
|
|
2530
|
-
logger.warn(`
|
|
2579
|
+
logger.warn(`items.columns precisa ser uma lista \u2014 declara\xE7\xE3o de colunas ignorada`);
|
|
2531
2580
|
return void 0;
|
|
2532
2581
|
}
|
|
2533
2582
|
const result = [];
|
|
@@ -2537,15 +2586,15 @@ function normalizeItemColumns(slug, input, logger) {
|
|
|
2537
2586
|
const rawLabel = typeof entry === "object" && entry !== null ? entry.label : void 0;
|
|
2538
2587
|
const key = typeof rawKey === "string" ? rawKey.trim() : "";
|
|
2539
2588
|
if (!KEY_RE.test(key)) {
|
|
2540
|
-
logger.warn(`
|
|
2589
|
+
logger.warn(`coluna de item inv\xE1lida ignorada (${JSON.stringify(rawKey)}) \u2014 use letras, n\xFAmeros, ponto, h\xEDfen e underline`);
|
|
2541
2590
|
continue;
|
|
2542
2591
|
}
|
|
2543
2592
|
if (seen.has(key)) {
|
|
2544
|
-
logger.warn(`
|
|
2593
|
+
logger.warn(`coluna de item "${key}" repetida ignorada`);
|
|
2545
2594
|
continue;
|
|
2546
2595
|
}
|
|
2547
2596
|
if (result.length >= MAX_COLUMNS) {
|
|
2548
|
-
logger.warn(`
|
|
2597
|
+
logger.warn(`no m\xE1ximo ${MAX_COLUMNS} colunas de item \u2014 "${key}" em diante ignoradas`);
|
|
2549
2598
|
break;
|
|
2550
2599
|
}
|
|
2551
2600
|
seen.add(key);
|
|
@@ -2557,15 +2606,15 @@ function normalizeItemColumns(slug, input, logger) {
|
|
|
2557
2606
|
|
|
2558
2607
|
// src/tasks/local.ts
|
|
2559
2608
|
var LocalRunner = class {
|
|
2560
|
-
constructor(
|
|
2561
|
-
this.
|
|
2609
|
+
constructor(handler, deps) {
|
|
2610
|
+
this.handler = handler;
|
|
2562
2611
|
this.deps = deps;
|
|
2563
|
-
this.backoff = new Backoff(deps.logger,
|
|
2612
|
+
this.backoff = new Backoff(deps.logger, "cria\xE7\xE3o da run");
|
|
2564
2613
|
this.done = new Promise((resolve2) => {
|
|
2565
2614
|
this.resolveDone = resolve2;
|
|
2566
2615
|
});
|
|
2567
2616
|
}
|
|
2568
|
-
|
|
2617
|
+
handler;
|
|
2569
2618
|
deps;
|
|
2570
2619
|
abort = new AbortController();
|
|
2571
2620
|
backoff;
|
|
@@ -2576,17 +2625,17 @@ var LocalRunner = class {
|
|
|
2576
2625
|
try {
|
|
2577
2626
|
const run = await this.createRun();
|
|
2578
2627
|
if (!run) return;
|
|
2579
|
-
this.deps.logger.debug(`run ${run.id} aberta
|
|
2628
|
+
this.deps.logger.debug(`run ${run.id} aberta (modo start)`);
|
|
2580
2629
|
await executeRun({
|
|
2581
2630
|
run,
|
|
2582
|
-
|
|
2631
|
+
handler: this.handler,
|
|
2583
2632
|
api: this.deps.api,
|
|
2584
2633
|
client: this.deps.client,
|
|
2585
2634
|
logger: this.deps.logger,
|
|
2586
2635
|
tracker: this.deps.tracker
|
|
2587
2636
|
});
|
|
2588
2637
|
} catch (error) {
|
|
2589
|
-
this.deps.logger.error(
|
|
2638
|
+
this.deps.logger.error("erro inesperado na execu\xE7\xE3o do agente:", error);
|
|
2590
2639
|
} finally {
|
|
2591
2640
|
this.resolveDone();
|
|
2592
2641
|
}
|
|
@@ -2599,7 +2648,7 @@ var LocalRunner = class {
|
|
|
2599
2648
|
for (; ; ) {
|
|
2600
2649
|
if (this.abort.signal.aborted) return void 0;
|
|
2601
2650
|
try {
|
|
2602
|
-
return await this.deps.api.createRun({
|
|
2651
|
+
return await this.deps.api.createRun({ instance: this.deps.instanceInfo() }, this.abort.signal);
|
|
2603
2652
|
} catch (error) {
|
|
2604
2653
|
if (this.abort.signal.aborted) return void 0;
|
|
2605
2654
|
await abortableSleep(this.backoff.next(error), this.abort.signal);
|
|
@@ -2609,45 +2658,6 @@ var LocalRunner = class {
|
|
|
2609
2658
|
};
|
|
2610
2659
|
|
|
2611
2660
|
// src/tasks/registry.ts
|
|
2612
|
-
var TaskRegistry = class {
|
|
2613
|
-
tasks = /* @__PURE__ */ new Map();
|
|
2614
|
-
add(task) {
|
|
2615
|
-
if (this.tasks.has(task.slug)) {
|
|
2616
|
-
throw new Error(`[hobots] task "${task.slug}" j\xE1 registrada \u2014 cada slug s\xF3 pode ter um handler (register ou start)`);
|
|
2617
|
-
}
|
|
2618
|
-
this.tasks.set(task.slug, task);
|
|
2619
|
-
}
|
|
2620
|
-
get(slug) {
|
|
2621
|
-
return this.tasks.get(slug);
|
|
2622
|
-
}
|
|
2623
|
-
get size() {
|
|
2624
|
-
return this.tasks.size;
|
|
2625
|
-
}
|
|
2626
|
-
/** Tasks que chegam pelo poll (register). */
|
|
2627
|
-
remoteTasks() {
|
|
2628
|
-
return [...this.tasks.values()].filter((task) => task.mode === "remote");
|
|
2629
|
-
}
|
|
2630
|
-
/** Tasks abertas pelo próprio bot (start). */
|
|
2631
|
-
localTasks() {
|
|
2632
|
-
return [...this.tasks.values()].filter((task) => task.mode === "local");
|
|
2633
|
-
}
|
|
2634
|
-
/** Todos os slugs, para a presença da instância (`instance.tasks`). */
|
|
2635
|
-
allSlugs() {
|
|
2636
|
-
return [...this.tasks.keys()];
|
|
2637
|
-
}
|
|
2638
|
-
/** Colunas de itens declaradas no código, por slug (`instance.task_items`); undefined se nenhuma task declarou. */
|
|
2639
|
-
itemDeclarations() {
|
|
2640
|
-
let result;
|
|
2641
|
-
for (const task of this.tasks.values()) {
|
|
2642
|
-
if (!task.options.itemColumns) continue;
|
|
2643
|
-
(result ??= {})[task.slug] = { attributes: task.options.itemColumns };
|
|
2644
|
-
}
|
|
2645
|
-
return result;
|
|
2646
|
-
}
|
|
2647
|
-
clear() {
|
|
2648
|
-
this.tasks.clear();
|
|
2649
|
-
}
|
|
2650
|
-
};
|
|
2651
2661
|
var RunTracker = class {
|
|
2652
2662
|
constructor(api) {
|
|
2653
2663
|
this.api = api;
|
|
@@ -2668,12 +2678,6 @@ var RunTracker = class {
|
|
|
2668
2678
|
get activeCount() {
|
|
2669
2679
|
return this.reporters.size;
|
|
2670
2680
|
}
|
|
2671
|
-
/** Runs ativas de uma task (concorrência por task no poll). */
|
|
2672
|
-
activeFor(slug) {
|
|
2673
|
-
let count = 0;
|
|
2674
|
-
for (const reporter of this.reporters) if (reporter.context.task === slug) count++;
|
|
2675
|
-
return count;
|
|
2676
|
-
}
|
|
2677
2681
|
/** A única run ativa, quando há exatamente uma — o que `hobots.*` usa fora de um contexto async. */
|
|
2678
2682
|
singleActive() {
|
|
2679
2683
|
if (this.reporters.size !== 1) return void 0;
|
|
@@ -2762,40 +2766,13 @@ var DEFAULT_RETRIES2 = 1;
|
|
|
2762
2766
|
var DEFAULT_RETRY_DELAY_MS2 = 5e3;
|
|
2763
2767
|
var DEFAULT_CANCEL_GRACE_MS = 1e4;
|
|
2764
2768
|
var MAX_CONCURRENCY = 5;
|
|
2765
|
-
var MAX_SLUG_CHARS = 255;
|
|
2766
|
-
var PROCESS_LEVEL_KEYS = [
|
|
2767
|
-
"clientSecret",
|
|
2768
|
-
"instanceId",
|
|
2769
|
-
"environment",
|
|
2770
|
-
"release",
|
|
2771
|
-
"tags",
|
|
2772
|
-
"debug",
|
|
2773
|
-
"machineMetrics",
|
|
2774
|
-
"captureUnhandled",
|
|
2775
|
-
"installExitHandlers"
|
|
2776
|
-
];
|
|
2777
2769
|
var runtime;
|
|
2778
2770
|
var warnedNoRuntime = false;
|
|
2779
2771
|
function hostname() {
|
|
2780
2772
|
if (!isNode()) return void 0;
|
|
2781
2773
|
return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
|
|
2782
2774
|
}
|
|
2783
|
-
function
|
|
2784
|
-
if (a === b) return true;
|
|
2785
|
-
if (a && b && typeof a === "object" && typeof b === "object") return JSON.stringify(a) === JSON.stringify(b);
|
|
2786
|
-
return false;
|
|
2787
|
-
}
|
|
2788
|
-
function ensureRuntime(options) {
|
|
2789
|
-
if (runtime) {
|
|
2790
|
-
for (const key of PROCESS_LEVEL_KEYS) {
|
|
2791
|
-
if (options[key] !== void 0 && !sameValue(options[key], runtime.options[key])) {
|
|
2792
|
-
runtime.logger.warn(
|
|
2793
|
-
`op\xE7\xE3o "${key}" divergente ignorada \u2014 o primeiro register()/start() define as op\xE7\xF5es do processo`
|
|
2794
|
-
);
|
|
2795
|
-
}
|
|
2796
|
-
}
|
|
2797
|
-
return runtime;
|
|
2798
|
-
}
|
|
2775
|
+
function createRuntime(options, handler) {
|
|
2799
2776
|
const config = resolveConfig({ clientSecret: options.clientSecret });
|
|
2800
2777
|
const logger = makeLogger(options.debug ?? false);
|
|
2801
2778
|
const environment = resolveEnvironmentOption(options.environment);
|
|
@@ -2803,19 +2780,19 @@ function ensureRuntime(options) {
|
|
|
2803
2780
|
{ environment, release: options.release, tags: options.tags, debug: options.debug },
|
|
2804
2781
|
config
|
|
2805
2782
|
);
|
|
2806
|
-
const api = new TasksApi({ baseUrl: config.
|
|
2807
|
-
|
|
2783
|
+
const api = new TasksApi({ baseUrl: config.runsBaseUrl, key: config.key, logger });
|
|
2784
|
+
const rt = {
|
|
2808
2785
|
options: { ...options, clientSecret: void 0 },
|
|
2809
2786
|
config,
|
|
2810
2787
|
client,
|
|
2811
2788
|
logger,
|
|
2812
2789
|
api,
|
|
2813
|
-
|
|
2790
|
+
handler,
|
|
2814
2791
|
tracker: new RunTracker(api),
|
|
2815
2792
|
sampler: void 0,
|
|
2816
2793
|
heartbeat: void 0,
|
|
2817
2794
|
agent: void 0,
|
|
2818
|
-
|
|
2795
|
+
local: void 0,
|
|
2819
2796
|
instanceId: resolveInstanceId(options.instanceId),
|
|
2820
2797
|
environment,
|
|
2821
2798
|
pollIntervalMs: void 0,
|
|
@@ -2825,24 +2802,19 @@ function ensureRuntime(options) {
|
|
|
2825
2802
|
uninstallGlobalHandlers: options.captureUnhandled !== false ? installGlobalHandlers(client) : void 0,
|
|
2826
2803
|
uninstallShutdown: void 0
|
|
2827
2804
|
};
|
|
2828
|
-
runtime.options.clientSecret = options.clientSecret;
|
|
2829
2805
|
logger.debug("inicializado", { endpoint: config.ingestEndpoint });
|
|
2830
|
-
return
|
|
2806
|
+
return rt;
|
|
2831
2807
|
}
|
|
2832
|
-
function
|
|
2833
|
-
if (typeof slug !== "string" || slug.trim().length === 0 || slug.length > MAX_SLUG_CHARS) {
|
|
2834
|
-
throw new Error(`[hobots] ${fn}(): o slug da task precisa ser uma string de 1 a ${MAX_SLUG_CHARS} caracteres`);
|
|
2835
|
-
}
|
|
2808
|
+
function validateHandler(handler, fn) {
|
|
2836
2809
|
if (typeof handler !== "function") {
|
|
2837
|
-
throw new Error(`[hobots] ${fn}(
|
|
2810
|
+
throw new Error(`[hobots] ${fn}(): o handler precisa ser uma fun\xE7\xE3o \u2014 ${fn}(async (params) => { ... })`);
|
|
2838
2811
|
}
|
|
2839
|
-
return slug.trim();
|
|
2840
2812
|
}
|
|
2841
2813
|
function clampConcurrency(value) {
|
|
2842
2814
|
if (value === void 0 || !Number.isFinite(value)) return 1;
|
|
2843
2815
|
return Math.min(MAX_CONCURRENCY, Math.max(1, Math.floor(value)));
|
|
2844
2816
|
}
|
|
2845
|
-
function
|
|
2817
|
+
function resolveRunOptions(options, mode, logger) {
|
|
2846
2818
|
const retries = options.retries === void 0 || !Number.isFinite(options.retries) ? DEFAULT_RETRIES2 : Math.max(0, Math.floor(options.retries));
|
|
2847
2819
|
return {
|
|
2848
2820
|
retries,
|
|
@@ -2852,7 +2824,7 @@ function resolveTaskOptions(slug, options, mode, logger) {
|
|
|
2852
2824
|
logFlushIntervalMs: options.logFlushIntervalMs,
|
|
2853
2825
|
cancelCheckIntervalMs: options.cancelCheckIntervalMs,
|
|
2854
2826
|
cancelGraceMs: options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS,
|
|
2855
|
-
itemColumns: normalizeItemColumns(
|
|
2827
|
+
itemColumns: normalizeItemColumns(options.items, logger)
|
|
2856
2828
|
};
|
|
2857
2829
|
}
|
|
2858
2830
|
function instanceInfo(rt) {
|
|
@@ -2862,10 +2834,10 @@ function instanceInfo(rt) {
|
|
|
2862
2834
|
host: hostname(),
|
|
2863
2835
|
// ambiente da instância: junto do heartbeat, é como o servidor sabe onde o robô roda
|
|
2864
2836
|
environment: rt.environment,
|
|
2865
|
-
//
|
|
2866
|
-
|
|
2837
|
+
// como esta instância trabalha — o painel mostra "sob demanda" / "sempre ligado"
|
|
2838
|
+
mode: rt.handler.mode === "remote" ? "on_demand" : "always_on",
|
|
2867
2839
|
// colunas da aba "itens" declaradas no código — a plataforma usa enquanto o usuário não personalizar
|
|
2868
|
-
|
|
2840
|
+
items: rt.handler.options.itemColumns ? { attributes: rt.handler.options.itemColumns } : void 0,
|
|
2869
2841
|
// o servidor calcula a tolerância de offline (3×) a partir disto em agentes que fazem
|
|
2870
2842
|
// poll; clampado à faixa aceita pelo schema (1s..15min)
|
|
2871
2843
|
poll_interval_ms: rt.agent ? Math.min(Math.max(rt.agent.pollIntervalMs, 1e3), 9e5) : void 0,
|
|
@@ -2878,22 +2850,22 @@ function ensureAgent(rt) {
|
|
|
2878
2850
|
api: rt.api,
|
|
2879
2851
|
client: rt.client,
|
|
2880
2852
|
logger: rt.logger,
|
|
2881
|
-
|
|
2853
|
+
handler: rt.handler,
|
|
2882
2854
|
tracker: rt.tracker,
|
|
2883
2855
|
instanceInfo: () => instanceInfo(rt),
|
|
2884
2856
|
pollIntervalMs: rt.pollIntervalMs
|
|
2885
2857
|
});
|
|
2886
2858
|
return rt.agent;
|
|
2887
2859
|
}
|
|
2888
|
-
function launchLocal(rt
|
|
2889
|
-
const runner = new LocalRunner(
|
|
2860
|
+
function launchLocal(rt) {
|
|
2861
|
+
const runner = new LocalRunner(rt.handler, {
|
|
2890
2862
|
api: rt.api,
|
|
2891
2863
|
client: rt.client,
|
|
2892
2864
|
logger: rt.logger,
|
|
2893
2865
|
tracker: rt.tracker,
|
|
2894
2866
|
instanceInfo: () => instanceInfo(rt)
|
|
2895
2867
|
});
|
|
2896
|
-
rt.
|
|
2868
|
+
rt.local = runner;
|
|
2897
2869
|
void runner.run();
|
|
2898
2870
|
}
|
|
2899
2871
|
async function boot(rt) {
|
|
@@ -2915,8 +2887,8 @@ async function boot(rt) {
|
|
|
2915
2887
|
if (rt.options.installExitHandlers !== false) {
|
|
2916
2888
|
rt.uninstallShutdown = installShutdownHandlers(rt.tracker);
|
|
2917
2889
|
}
|
|
2918
|
-
if (rt.
|
|
2919
|
-
|
|
2890
|
+
if (rt.handler.mode === "remote") ensureAgent(rt).start();
|
|
2891
|
+
else launchLocal(rt);
|
|
2920
2892
|
}
|
|
2921
2893
|
function scheduleBoot(rt) {
|
|
2922
2894
|
if (rt.bootScheduled) return;
|
|
@@ -2925,34 +2897,23 @@ function scheduleBoot(rt) {
|
|
|
2925
2897
|
rt.bootPromise = boot(rt).catch((error) => rt.logger.error("falha ao iniciar o SDK:", error));
|
|
2926
2898
|
});
|
|
2927
2899
|
}
|
|
2928
|
-
function
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
if (rt.pollIntervalMs === void 0 && !rt.agent) rt.pollIntervalMs = options.pollIntervalMs;
|
|
2933
|
-
else if (rt.pollIntervalMs !== options.pollIntervalMs) {
|
|
2934
|
-
rt.logger.warn('op\xE7\xE3o "pollIntervalMs" divergente ignorada \u2014 o primeiro register() define o intervalo do poll');
|
|
2935
|
-
}
|
|
2936
|
-
}
|
|
2937
|
-
const registered = { slug, mode, handler, options: resolveTaskOptions(slug, options, mode, rt.logger) };
|
|
2938
|
-
rt.registry.add(registered);
|
|
2939
|
-
if (!rt.booted) {
|
|
2940
|
-
scheduleBoot(rt);
|
|
2941
|
-
return;
|
|
2942
|
-
}
|
|
2943
|
-
if (mode === "remote") {
|
|
2944
|
-
const agent = ensureAgent(rt);
|
|
2945
|
-
agent.start();
|
|
2946
|
-
agent.wake();
|
|
2947
|
-
} else {
|
|
2948
|
-
launchLocal(rt, registered);
|
|
2900
|
+
function startAgent(fn, mode, handler, options) {
|
|
2901
|
+
validateHandler(handler, fn);
|
|
2902
|
+
if (runtime) {
|
|
2903
|
+
throw new Error(`[hobots] ${fn}(): o agente j\xE1 foi iniciado \u2014 register()/start() s\xF3 pode ser chamado uma vez por processo (chame hobots.close() antes de iniciar de novo)`);
|
|
2949
2904
|
}
|
|
2905
|
+
const logger = makeLogger(options.debug ?? false);
|
|
2906
|
+
const agentHandler = { mode, handler, options: resolveRunOptions(options, mode, logger) };
|
|
2907
|
+
const rt = createRuntime(options, agentHandler);
|
|
2908
|
+
if (mode === "remote" && options.pollIntervalMs !== void 0) rt.pollIntervalMs = options.pollIntervalMs;
|
|
2909
|
+
runtime = rt;
|
|
2910
|
+
scheduleBoot(rt);
|
|
2950
2911
|
}
|
|
2951
|
-
function register(
|
|
2952
|
-
|
|
2912
|
+
function register(handler, options = {}) {
|
|
2913
|
+
startAgent("register", "remote", handler, options);
|
|
2953
2914
|
}
|
|
2954
|
-
function start(
|
|
2955
|
-
|
|
2915
|
+
function start(handler, options = {}) {
|
|
2916
|
+
startAgent("start", "local", handler, options);
|
|
2956
2917
|
}
|
|
2957
2918
|
function currentReporter() {
|
|
2958
2919
|
return currentRunReporter() ?? runtime?.tracker.singleActive();
|
|
@@ -2966,23 +2927,18 @@ function toRunLogLevel(type) {
|
|
|
2966
2927
|
function forwardAttachment(rt, attachment, message, type) {
|
|
2967
2928
|
const reporter = currentReporter();
|
|
2968
2929
|
if (!reporter) {
|
|
2969
|
-
rt.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o
|
|
2930
|
+
rt.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o");
|
|
2970
2931
|
return;
|
|
2971
2932
|
}
|
|
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
|
-
});
|
|
2933
|
+
const withMeta = typeof attachment === "object" && !isImageAttachment(attachment) && "image" in attachment ? image(attachment.image, { caption: attachment.caption, contentType: attachment.contentType }) : attachment;
|
|
2934
|
+
reporter.log(toRunLogLevel(type), message, withMeta);
|
|
2979
2935
|
}
|
|
2980
2936
|
function captureException(error, hint) {
|
|
2981
2937
|
const rt = runtime;
|
|
2982
2938
|
if (!rt) {
|
|
2983
2939
|
if (!warnedNoRuntime) {
|
|
2984
2940
|
warnedNoRuntime = true;
|
|
2985
|
-
console.warn("[hobots] captureException antes de
|
|
2941
|
+
console.warn("[hobots] captureException antes de iniciar o agente (register/start) \u2014 evento descartado");
|
|
2986
2942
|
}
|
|
2987
2943
|
return void 0;
|
|
2988
2944
|
}
|
|
@@ -2998,7 +2954,7 @@ async function close(timeoutMs = 3e4) {
|
|
|
2998
2954
|
runtime = void 0;
|
|
2999
2955
|
await rt.bootPromise;
|
|
3000
2956
|
if (rt.agent) await rt.agent.stop(0);
|
|
3001
|
-
|
|
2957
|
+
rt.local?.stop();
|
|
3002
2958
|
const idle = await rt.tracker.waitIdle(timeoutMs);
|
|
3003
2959
|
if (!idle) {
|
|
3004
2960
|
rt.logger.warn(`${rt.tracker.activeCount} run(s) ainda em execu\xE7\xE3o ap\xF3s ${timeoutMs}ms \u2014 abandonando a espera`);
|
|
@@ -3016,18 +2972,21 @@ var warned = /* @__PURE__ */ new Set();
|
|
|
3016
2972
|
function warnOutsideRun(member) {
|
|
3017
2973
|
if (warned.has(member)) return;
|
|
3018
2974
|
warned.add(member);
|
|
3019
|
-
const message = `hobots.${member} usado fora de uma execu\xE7\xE3o
|
|
2975
|
+
const message = `hobots.${member} usado fora de uma execu\xE7\xE3o \u2014 ignorado (inicie o agente com register()/start() e use dentro do handler)`;
|
|
3020
2976
|
const logger = activeLogger();
|
|
3021
2977
|
if (logger) logger.warn(message);
|
|
3022
2978
|
else console.warn(`[hobots] ${message}`);
|
|
3023
2979
|
}
|
|
3024
|
-
var
|
|
2980
|
+
var fallbackLogAt = (level) => (message, ...rest) => {
|
|
2981
|
+
const { text, image: dropped } = splitLogArgs(message, rest);
|
|
2982
|
+
printRunLog(level, dropped ? `${text} [imagem descartada]` : text);
|
|
2983
|
+
};
|
|
3025
2984
|
var fallbackLog = {
|
|
3026
|
-
debug: (
|
|
3027
|
-
info: (
|
|
3028
|
-
warn: (
|
|
3029
|
-
error: (
|
|
3030
|
-
success: (
|
|
2985
|
+
debug: fallbackLogAt("debug"),
|
|
2986
|
+
info: fallbackLogAt("info"),
|
|
2987
|
+
warn: fallbackLogAt("warning"),
|
|
2988
|
+
error: fallbackLogAt("error"),
|
|
2989
|
+
success: fallbackLogAt("success")
|
|
3031
2990
|
};
|
|
3032
2991
|
var noop = () => void 0;
|
|
3033
2992
|
var fallbackEachSink = {
|
|
@@ -3037,7 +2996,7 @@ var fallbackEachSink = {
|
|
|
3037
2996
|
advance: noop,
|
|
3038
2997
|
checkCanceled: noop,
|
|
3039
2998
|
isAborted: () => false,
|
|
3040
|
-
logError: (message) => printRunLog(
|
|
2999
|
+
logError: (message) => printRunLog("error", message)
|
|
3041
3000
|
};
|
|
3042
3001
|
var fallbackItems = {
|
|
3043
3002
|
add: noop,
|
|
@@ -3064,7 +3023,6 @@ function method(value) {
|
|
|
3064
3023
|
var hobots = Object.defineProperties({}, {
|
|
3065
3024
|
// ── contexto da run atual (getters) ─────────────────────────────────────────
|
|
3066
3025
|
runId: getter("runId", () => ""),
|
|
3067
|
-
task: getter("task", () => ""),
|
|
3068
3026
|
params: getter("params", () => ({})),
|
|
3069
3027
|
log: getter("log", () => fallbackLog),
|
|
3070
3028
|
items: getter("items", () => fallbackItems),
|
|
@@ -3073,24 +3031,22 @@ var hobots = Object.defineProperties({}, {
|
|
|
3073
3031
|
signal: getter("signal", () => neverAborts),
|
|
3074
3032
|
throwIfCanceled: method(() => resolve("throwIfCanceled")?.throwIfCanceled()),
|
|
3075
3033
|
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
3034
|
retry: method((fn, options) => {
|
|
3080
3035
|
const reporter = currentReporter();
|
|
3081
3036
|
return reporter ? reporter.context.retry(fn, options) : retryWithBackoff(fn, options);
|
|
3082
3037
|
}),
|
|
3083
3038
|
// ── processo ────────────────────────────────────────────────────────────────
|
|
3084
|
-
register: method((
|
|
3085
|
-
register(
|
|
3039
|
+
register: method((handler, options) => {
|
|
3040
|
+
register(handler, options);
|
|
3086
3041
|
installGlobalHobots();
|
|
3087
3042
|
}),
|
|
3088
|
-
start: method((
|
|
3089
|
-
start(
|
|
3043
|
+
start: method((handler, options) => {
|
|
3044
|
+
start(handler, options);
|
|
3090
3045
|
installGlobalHobots();
|
|
3091
3046
|
}),
|
|
3092
3047
|
close: method((timeoutMs) => close(timeoutMs)),
|
|
3093
|
-
captureException: method((error, hint) => captureException(error, hint))
|
|
3048
|
+
captureException: method((error, hint) => captureException(error, hint)),
|
|
3049
|
+
image: method(image)
|
|
3094
3050
|
});
|
|
3095
3051
|
function installGlobalHobots() {
|
|
3096
3052
|
if (globalThis.hobots === hobots) return;
|
|
@@ -3101,6 +3057,7 @@ var start2 = hobots.start;
|
|
|
3101
3057
|
var close2 = hobots.close;
|
|
3102
3058
|
var captureException2 = hobots.captureException;
|
|
3103
3059
|
var retry = hobots.retry;
|
|
3060
|
+
var image2 = hobots.image;
|
|
3104
3061
|
|
|
3105
3062
|
// src/index.ts
|
|
3106
3063
|
var src_default = hobots;
|