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.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 ? "
|
|
60
|
+
var SDK_VERSION = true ? "4.0.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
|
}
|
|
@@ -517,7 +514,7 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
517
514
|
const origin = fromOption ? "clientSecret" : `env ${CLIENT_SECRET_ENV}`;
|
|
518
515
|
if (!key) {
|
|
519
516
|
throw new Error(
|
|
520
|
-
`${prefix} Client Secret ausente \u2014 defina a env ${CLIENT_SECRET_ENV} (ou passe clientSecret nas op\xE7\xF5es
|
|
517
|
+
`${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`
|
|
521
518
|
);
|
|
522
519
|
}
|
|
523
520
|
if (!SECRET_PATTERN.test(key)) {
|
|
@@ -530,7 +527,7 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
530
527
|
key,
|
|
531
528
|
baseUrl,
|
|
532
529
|
ingestEndpoint: `${baseUrl}/v1/ingest`,
|
|
533
|
-
|
|
530
|
+
runsBaseUrl: `${baseUrl}/v1/runs`
|
|
534
531
|
};
|
|
535
532
|
}
|
|
536
533
|
|
|
@@ -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),
|
|
@@ -893,7 +891,8 @@ var TasksApi = class {
|
|
|
893
891
|
const { signal: timeoutSignal, cleanup } = withTimeoutSignal(UPDATE_TIMEOUT_MS, signal);
|
|
894
892
|
let response;
|
|
895
893
|
try {
|
|
896
|
-
response = await fetch(`${this.baseUrl}
|
|
894
|
+
response = await fetch(`${this.baseUrl}`, {
|
|
895
|
+
redirect: "error",
|
|
897
896
|
method: "POST",
|
|
898
897
|
headers: this.headers(),
|
|
899
898
|
body: JSON.stringify(request),
|
|
@@ -906,12 +905,11 @@ var TasksApi = class {
|
|
|
906
905
|
throw new TasksApiError(`cria\xE7\xE3o da run falhou (HTTP ${response.status})`, response.status);
|
|
907
906
|
}
|
|
908
907
|
const body = await response.json().catch(() => null);
|
|
909
|
-
if (typeof body?.id !== "string"
|
|
910
|
-
throw new TasksApiError("resposta inv\xE1lida da cria\xE7\xE3o de run (id
|
|
908
|
+
if (typeof body?.id !== "string") {
|
|
909
|
+
throw new TasksApiError("resposta inv\xE1lida da cria\xE7\xE3o de run (id ausente)", 0);
|
|
911
910
|
}
|
|
912
911
|
return {
|
|
913
912
|
id: body.id,
|
|
914
|
-
task: body.task,
|
|
915
913
|
params: body.params && typeof body.params === "object" && !Array.isArray(body.params) ? body.params : {},
|
|
916
914
|
form: null,
|
|
917
915
|
source: typeof body.source === "string" ? body.source : "agent",
|
|
@@ -926,9 +924,9 @@ var TasksApi = class {
|
|
|
926
924
|
}
|
|
927
925
|
const valid = [];
|
|
928
926
|
for (const run of runs.slice(0, Math.max(0, max))) {
|
|
929
|
-
if (typeof run?.id !== "string"
|
|
927
|
+
if (typeof run?.id !== "string") continue;
|
|
930
928
|
const params = run.params && typeof run.params === "object" && !Array.isArray(run.params) ? run.params : {};
|
|
931
|
-
valid.push({ ...run, id: run.id,
|
|
929
|
+
valid.push({ ...run, id: run.id, params });
|
|
932
930
|
}
|
|
933
931
|
if (valid.length < runs.length) {
|
|
934
932
|
this.logger.warn(`poll retornou ${runs.length - valid.length} run(s) inv\xE1lida(s)/excedente(s) \u2014 descartadas`);
|
|
@@ -943,7 +941,8 @@ var TasksApi = class {
|
|
|
943
941
|
let response;
|
|
944
942
|
try {
|
|
945
943
|
response = await fetchWithRetry(
|
|
946
|
-
() => fetch(`${this.baseUrl}
|
|
944
|
+
() => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}`, {
|
|
945
|
+
redirect: "error",
|
|
947
946
|
method: "PATCH",
|
|
948
947
|
headers: this.headers(),
|
|
949
948
|
body: JSON.stringify(update),
|
|
@@ -964,7 +963,8 @@ var TasksApi = class {
|
|
|
964
963
|
* que a run já foi cancelada/finalizada, o que é aceitável nesse caminho.
|
|
965
964
|
*/
|
|
966
965
|
async updateRunOnce(runId, update, timeoutMs = 2e3) {
|
|
967
|
-
await fetch(`${this.baseUrl}
|
|
966
|
+
await fetch(`${this.baseUrl}/${encodeURIComponent(runId)}`, {
|
|
967
|
+
redirect: "error",
|
|
968
968
|
method: "PATCH",
|
|
969
969
|
headers: this.headers(),
|
|
970
970
|
body: JSON.stringify(update),
|
|
@@ -980,7 +980,8 @@ var TasksApi = class {
|
|
|
980
980
|
let response;
|
|
981
981
|
try {
|
|
982
982
|
response = await fetchWithRetry(
|
|
983
|
-
() => fetch(`${this.baseUrl}
|
|
983
|
+
() => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/items`, {
|
|
984
|
+
redirect: "error",
|
|
984
985
|
method: "POST",
|
|
985
986
|
headers: this.headers(),
|
|
986
987
|
body: JSON.stringify(request),
|
|
@@ -1009,7 +1010,8 @@ var TasksApi = class {
|
|
|
1009
1010
|
() => withRequestTimeout(
|
|
1010
1011
|
UPDATE_TIMEOUT_MS,
|
|
1011
1012
|
signal,
|
|
1012
|
-
(timeoutSignal) => fetch(`${this.baseUrl}
|
|
1013
|
+
(timeoutSignal) => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/attachments`, {
|
|
1014
|
+
redirect: "error",
|
|
1013
1015
|
method: "POST",
|
|
1014
1016
|
headers: this.headers(),
|
|
1015
1017
|
body: JSON.stringify(request),
|
|
@@ -1042,6 +1044,7 @@ var TasksApi = class {
|
|
|
1042
1044
|
UPLOAD_TIMEOUT_MS,
|
|
1043
1045
|
signal,
|
|
1044
1046
|
(timeoutSignal) => fetch(url, {
|
|
1047
|
+
redirect: "error",
|
|
1045
1048
|
method: "PUT",
|
|
1046
1049
|
headers,
|
|
1047
1050
|
// BodyInit não aceita Uint8Array<ArrayBufferLike> em todos os lib.dom; o buffer é o mesmo
|
|
@@ -1058,8 +1061,9 @@ var TasksApi = class {
|
|
|
1058
1061
|
/** Marca o anexo como enviado (ou falho). Best-effort — a leitura não depende disso. */
|
|
1059
1062
|
async confirmAttachment(runId, attachmentId, request) {
|
|
1060
1063
|
const response = await fetch(
|
|
1061
|
-
`${this.baseUrl}
|
|
1064
|
+
`${this.baseUrl}/${encodeURIComponent(runId)}/attachments/${encodeURIComponent(attachmentId)}`,
|
|
1062
1065
|
{
|
|
1066
|
+
redirect: "error",
|
|
1063
1067
|
method: "PATCH",
|
|
1064
1068
|
headers: this.headers(),
|
|
1065
1069
|
body: JSON.stringify(request),
|
|
@@ -1142,22 +1146,22 @@ var ItemHandle = class {
|
|
|
1142
1146
|
sink;
|
|
1143
1147
|
reported = false;
|
|
1144
1148
|
status;
|
|
1145
|
-
succeeded(payload) {
|
|
1146
|
-
this.record("succeeded", void 0, payload);
|
|
1149
|
+
succeeded(payload, options) {
|
|
1150
|
+
this.record("succeeded", void 0, payload, options?.image);
|
|
1147
1151
|
}
|
|
1148
|
-
failed(message, payload) {
|
|
1149
|
-
this.record("failed", message, payload);
|
|
1152
|
+
failed(message, payload, options) {
|
|
1153
|
+
this.record("failed", message, payload, options?.image);
|
|
1150
1154
|
if (this.step && this.step.status !== "error") this.step.setStatus("error", message);
|
|
1151
1155
|
}
|
|
1152
|
-
occurrence(message, payload) {
|
|
1153
|
-
this.record("occurrence", message, payload);
|
|
1156
|
+
occurrence(message, payload, options) {
|
|
1157
|
+
this.record("occurrence", message, payload, options?.image);
|
|
1154
1158
|
if (this.step && this.step.status === "ok") this.step.setStatus("warning", message);
|
|
1155
1159
|
}
|
|
1156
|
-
record(status, message, payload) {
|
|
1160
|
+
record(status, message, payload, image3) {
|
|
1157
1161
|
this.reported = true;
|
|
1158
1162
|
this.status = status;
|
|
1159
1163
|
const merged = { ...this.basePayload ?? {}, ...payload ?? {} };
|
|
1160
|
-
this.sink.report(status, message, Object.keys(merged).length > 0 ? merged : void 0, this.id);
|
|
1164
|
+
this.sink.report(status, message, Object.keys(merged).length > 0 ? merged : void 0, this.id, image3);
|
|
1161
1165
|
}
|
|
1162
1166
|
};
|
|
1163
1167
|
function resolveId(options, element, index) {
|
|
@@ -1237,6 +1241,27 @@ var MAX_ATTACHMENTS_PER_LOG_ENTRY = WIRE_LIMITS.MAX_ATTACHMENTS_PER_LOG_ENTRY;
|
|
|
1237
1241
|
var MAX_CONCURRENT_UPLOADS = 3;
|
|
1238
1242
|
var MAX_CAPTION_CHARS = WIRE_LIMITS.MAX_CAPTION_CHARS;
|
|
1239
1243
|
var ATTACHMENT_DRAIN_MS = 15e3;
|
|
1244
|
+
var IMAGE_BRAND = /* @__PURE__ */ Symbol.for("hobots.image");
|
|
1245
|
+
var ImageAttachment = class {
|
|
1246
|
+
constructor(input, caption, contentType) {
|
|
1247
|
+
this.input = input;
|
|
1248
|
+
this.caption = caption;
|
|
1249
|
+
this.contentType = contentType;
|
|
1250
|
+
}
|
|
1251
|
+
input;
|
|
1252
|
+
caption;
|
|
1253
|
+
contentType;
|
|
1254
|
+
[IMAGE_BRAND] = true;
|
|
1255
|
+
};
|
|
1256
|
+
function image(input, options = {}) {
|
|
1257
|
+
return new ImageAttachment(input, options.caption, options.contentType);
|
|
1258
|
+
}
|
|
1259
|
+
function isImageAttachment(value) {
|
|
1260
|
+
return typeof value === "object" && value !== null && value[IMAGE_BRAND] === true;
|
|
1261
|
+
}
|
|
1262
|
+
function toImageAttachment(value) {
|
|
1263
|
+
return isImageAttachment(value) ? value : new ImageAttachment(value);
|
|
1264
|
+
}
|
|
1240
1265
|
async function readImageFile(path) {
|
|
1241
1266
|
if (!isNode()) {
|
|
1242
1267
|
throw new Error("caminho de arquivo s\xF3 \xE9 suportado em Node");
|
|
@@ -1286,8 +1311,16 @@ function joinLogArgs(message, rest) {
|
|
|
1286
1311
|
if (rest.length === 0) return safeString(message);
|
|
1287
1312
|
return [message, ...rest].map(safeString).join(" ");
|
|
1288
1313
|
}
|
|
1289
|
-
function
|
|
1290
|
-
const
|
|
1314
|
+
function splitLogArgs(message, rest) {
|
|
1315
|
+
const args = [message, ...rest];
|
|
1316
|
+
const last = args[args.length - 1];
|
|
1317
|
+
if (!isImageAttachment(last)) return { text: joinLogArgs(message, rest) };
|
|
1318
|
+
const text = args.slice(0, -1);
|
|
1319
|
+
if (text.length === 0) return { text: last.caption !== void 0 ? safeString(last.caption) : "captura de tela", image: last };
|
|
1320
|
+
return { text: joinLogArgs(text[0], text.slice(1)), image: last };
|
|
1321
|
+
}
|
|
1322
|
+
function printRunLog(level, message) {
|
|
1323
|
+
const line = colorize(`[hobots] ${level.toUpperCase()}`, LOG_LEVEL_COLOR[level]);
|
|
1291
1324
|
const method2 = level === "warning" ? "warn" : level === "success" ? "info" : level;
|
|
1292
1325
|
console[method2](line, message);
|
|
1293
1326
|
}
|
|
@@ -1333,7 +1366,7 @@ function scrubPayloadValue(value, depth) {
|
|
|
1333
1366
|
return out;
|
|
1334
1367
|
}
|
|
1335
1368
|
var RunReporter = class {
|
|
1336
|
-
constructor(api, runId,
|
|
1369
|
+
constructor(api, runId, logger, tx, flushIntervalMs, cancelCheckIntervalMs, params = {}) {
|
|
1337
1370
|
this.api = api;
|
|
1338
1371
|
this.runId = runId;
|
|
1339
1372
|
this.logger = logger;
|
|
@@ -1342,18 +1375,23 @@ var RunReporter = class {
|
|
|
1342
1375
|
MAX_CANCEL_CHECK_INTERVAL_MS,
|
|
1343
1376
|
Math.max(MIN_CANCEL_CHECK_INTERVAL_MS, cancelCheckIntervalMs ?? DEFAULT_CANCEL_CHECK_INTERVAL_MS)
|
|
1344
1377
|
);
|
|
1378
|
+
const logAt = (level) => (message, ...rest) => {
|
|
1379
|
+
const { text, image: image3 } = splitLogArgs(message, rest);
|
|
1380
|
+
this.log(level, text, image3);
|
|
1381
|
+
};
|
|
1345
1382
|
const log = {
|
|
1346
|
-
debug: (
|
|
1347
|
-
info: (
|
|
1348
|
-
warn: (
|
|
1349
|
-
error: (
|
|
1350
|
-
success: (
|
|
1383
|
+
debug: logAt("debug"),
|
|
1384
|
+
info: logAt("info"),
|
|
1385
|
+
warn: logAt("warning"),
|
|
1386
|
+
error: logAt("error"),
|
|
1387
|
+
success: logAt("success")
|
|
1351
1388
|
};
|
|
1352
1389
|
tx.setLifecycleSink((level, message) => this.push(level, message));
|
|
1390
|
+
tx.setAttachmentSink((image3, ts) => this.queueAttachment(image3, ts));
|
|
1353
1391
|
const checkCanceled = () => this.checkCanceled();
|
|
1354
1392
|
tx.setStepGuard(checkCanceled);
|
|
1355
1393
|
const eachSink = {
|
|
1356
|
-
report: (status, message, payload, id) => this.addItem(status, message, payload, id),
|
|
1394
|
+
report: (status, message, payload, id, image3) => this.addItem(status, message, payload, id, image3),
|
|
1357
1395
|
startStep: (op, description) => tx.startChild(op, description),
|
|
1358
1396
|
declareTotal: (total) => {
|
|
1359
1397
|
if (this.progressTotal == null) this.setProgress(this.progressCurrent, total);
|
|
@@ -1366,19 +1404,19 @@ var RunReporter = class {
|
|
|
1366
1404
|
const items = {
|
|
1367
1405
|
add: (item) => {
|
|
1368
1406
|
checkCanceled();
|
|
1369
|
-
this.addItem(item.status, item.message, item.payload, item.id);
|
|
1407
|
+
this.addItem(item.status, item.message, item.payload, item.id, item.image);
|
|
1370
1408
|
},
|
|
1371
1409
|
succeeded: (payload, options) => {
|
|
1372
1410
|
checkCanceled();
|
|
1373
|
-
this.addItem("succeeded", void 0, payload, options?.id);
|
|
1411
|
+
this.addItem("succeeded", void 0, payload, options?.id, options?.image);
|
|
1374
1412
|
},
|
|
1375
1413
|
failed: (message, payload, options) => {
|
|
1376
1414
|
checkCanceled();
|
|
1377
|
-
this.addItem("failed", message, payload, options?.id);
|
|
1415
|
+
this.addItem("failed", message, payload, options?.id, options?.image);
|
|
1378
1416
|
},
|
|
1379
1417
|
occurrence: (message, payload, options) => {
|
|
1380
1418
|
checkCanceled();
|
|
1381
|
-
this.addItem("occurrence", message, payload, options?.id);
|
|
1419
|
+
this.addItem("occurrence", message, payload, options?.id, options?.image);
|
|
1382
1420
|
},
|
|
1383
1421
|
each: (list, handler, options = {}) => eachItem(list, handler, options, eachSink)
|
|
1384
1422
|
};
|
|
@@ -1410,7 +1448,6 @@ var RunReporter = class {
|
|
|
1410
1448
|
const sleep2 = (ms) => cancelableSleep(ms, signal);
|
|
1411
1449
|
this.context = {
|
|
1412
1450
|
runId,
|
|
1413
|
-
task,
|
|
1414
1451
|
params,
|
|
1415
1452
|
log,
|
|
1416
1453
|
items,
|
|
@@ -1419,7 +1456,6 @@ var RunReporter = class {
|
|
|
1419
1456
|
signal,
|
|
1420
1457
|
throwIfCanceled: checkCanceled,
|
|
1421
1458
|
sleep: sleep2,
|
|
1422
|
-
attach: (image, options) => this.attach(image, options),
|
|
1423
1459
|
retry: (fn, options) => retryWithBackoff(fn, { ...options, signal, wait: sleep2 })
|
|
1424
1460
|
};
|
|
1425
1461
|
}
|
|
@@ -1510,15 +1546,21 @@ var RunReporter = class {
|
|
|
1510
1546
|
this.clearFlushTimer();
|
|
1511
1547
|
return this.takeLogs().slice(-MAX_LOGS_PER_PATCH);
|
|
1512
1548
|
}
|
|
1513
|
-
|
|
1549
|
+
/**
|
|
1550
|
+
* Linha de log da run com eco no terminal. `image` (opcional) vira o anexo da linha:
|
|
1551
|
+
* o id entra na entrada ANTES de qualquer I/O — é isso que mantém a ordem dos logs
|
|
1552
|
+
* intacta e o handler livre da latência do upload.
|
|
1553
|
+
*/
|
|
1554
|
+
log(level, message, image3) {
|
|
1514
1555
|
if (this.dead) return;
|
|
1515
1556
|
const scrubbed = scrubSecrets(safeString(message));
|
|
1516
|
-
|
|
1517
|
-
this.
|
|
1557
|
+
const ts = nowISO();
|
|
1558
|
+
const attachmentId = image3 === void 0 ? void 0 : this.queueAttachment(image3, ts);
|
|
1559
|
+
printRunLog(level, attachmentId ? `${scrubbed} [imagem]` : scrubbed);
|
|
1560
|
+
this.push(level, scrubbed, true, attachmentId ? [attachmentId] : void 0, ts);
|
|
1518
1561
|
}
|
|
1519
1562
|
/** Bufferiza uma entrada de log sem eco no terminal (cap, truncate, flush). */
|
|
1520
|
-
push(level, message, alreadyScrubbed = false, attachments) {
|
|
1521
|
-
const ts = nowISO();
|
|
1563
|
+
push(level, message, alreadyScrubbed = false, attachments, ts = nowISO()) {
|
|
1522
1564
|
if (this.dead) return ts;
|
|
1523
1565
|
const scrubbed = alreadyScrubbed ? message : scrubSecrets(message);
|
|
1524
1566
|
this.buffer.push({
|
|
@@ -1668,7 +1710,7 @@ var RunReporter = class {
|
|
|
1668
1710
|
* de status, o servidor aceita itens mesmo com a run cancelada (trabalho já feito),
|
|
1669
1711
|
* então um reporter morto não descarta: só um kill() de exit path descarta.
|
|
1670
1712
|
*/
|
|
1671
|
-
addItem(status, message, payload, id) {
|
|
1713
|
+
addItem(status, message, payload, id, image3) {
|
|
1672
1714
|
if (this.itemsClosed || this.itemsUnsupported) return;
|
|
1673
1715
|
if (this.itemCount >= MAX_ITEMS_PER_RUN) {
|
|
1674
1716
|
if (!this.itemsQuotaExceeded) {
|
|
@@ -1693,13 +1735,16 @@ var RunReporter = class {
|
|
|
1693
1735
|
scrubbedPayload = void 0;
|
|
1694
1736
|
}
|
|
1695
1737
|
}
|
|
1738
|
+
const ts = nowISO();
|
|
1739
|
+
const attachmentId = image3 === void 0 ? void 0 : this.queueAttachment(image3, ts);
|
|
1696
1740
|
this.itemsBuffer.push({
|
|
1697
1741
|
id: id ? truncate(String(id), 255) : uuid4(),
|
|
1698
1742
|
seq: this.itemSeq++,
|
|
1699
1743
|
status,
|
|
1700
1744
|
...message ? { message: truncate(scrubSecrets(safeString(message)), MAX_ITEM_MESSAGE_CHARS) } : {},
|
|
1701
1745
|
...scrubbedPayload ? { payload: scrubbedPayload } : {},
|
|
1702
|
-
ts
|
|
1746
|
+
ts,
|
|
1747
|
+
...attachmentId ? { attachments: [attachmentId] } : {}
|
|
1703
1748
|
});
|
|
1704
1749
|
if (this.itemsBuffer.length >= ITEMS_FLUSH_AT_BUFFERED) {
|
|
1705
1750
|
this.flushItems();
|
|
@@ -1779,59 +1824,51 @@ var RunReporter = class {
|
|
|
1779
1824
|
}
|
|
1780
1825
|
// ── Anexos ───────────────────────────────────────────────────────────────────
|
|
1781
1826
|
/**
|
|
1782
|
-
*
|
|
1783
|
-
*
|
|
1784
|
-
*
|
|
1785
|
-
*
|
|
1827
|
+
* Pendura uma imagem num evento da run (log, item ou step): faz as checagens que não
|
|
1828
|
+
* dependem de I/O (reporter morto, instalação sem anexos, cota), reserva o id e dispara
|
|
1829
|
+
* o upload em segundo plano. Devolve o id — já referenciável pelo evento — ou undefined
|
|
1830
|
+
* quando a imagem foi descartada (sempre com aviso no log da run). Nunca lança: todo
|
|
1831
|
+
* caminho de erro do upload vira aviso. O hint `attachment` de `hobots.captureException`
|
|
1832
|
+
* também cai aqui via `log()`.
|
|
1786
1833
|
*/
|
|
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
|
-
}
|
|
1834
|
+
queueAttachment(imageIn, ts) {
|
|
1835
|
+
if (this.dead) return void 0;
|
|
1836
|
+
const image3 = toImageAttachment(imageIn);
|
|
1837
|
+
if (this.attachmentsUnsupported) return void 0;
|
|
1799
1838
|
if (this.attachmentCount >= MAX_ATTACHMENTS_PER_RUN) {
|
|
1800
|
-
this.
|
|
1801
|
-
|
|
1802
|
-
|
|
1839
|
+
if (this.attachmentCount === MAX_ATTACHMENTS_PER_RUN) {
|
|
1840
|
+
this.attachmentCount++;
|
|
1841
|
+
this.log("warning", `imagem descartada: limite de ${MAX_ATTACHMENTS_PER_RUN} por execu\xE7\xE3o atingido`);
|
|
1842
|
+
}
|
|
1843
|
+
return void 0;
|
|
1803
1844
|
}
|
|
1804
1845
|
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" }));
|
|
1846
|
+
const id = uuid4();
|
|
1847
|
+
const rawCaption = image3.caption !== void 0 ? safeString(image3.caption) : void 0;
|
|
1848
|
+
const caption = rawCaption ? truncate(scrubSecrets(rawCaption), MAX_CAPTION_CHARS) : void 0;
|
|
1849
|
+
const work = this.uploadAttachment(id, image3.input, { caption, logTs: ts, contentType: image3.contentType }).then(() => void 0).catch(() => void 0);
|
|
1813
1850
|
this.pendingAttachments.add(work);
|
|
1814
1851
|
void work.finally(() => this.pendingAttachments.delete(work));
|
|
1815
|
-
return
|
|
1852
|
+
return id;
|
|
1816
1853
|
}
|
|
1817
1854
|
/** Retorna o motivo da falha, ou undefined em sucesso. */
|
|
1818
|
-
async uploadAttachment(id,
|
|
1855
|
+
async uploadAttachment(id, image3, meta) {
|
|
1819
1856
|
let bytes;
|
|
1820
1857
|
try {
|
|
1821
|
-
bytes = await toBytes(
|
|
1858
|
+
bytes = await toBytes(image3);
|
|
1822
1859
|
} catch (error) {
|
|
1823
|
-
this.log("warning", `
|
|
1860
|
+
this.log("warning", `imagem n\xE3o p\xF4de ser lida: ${String(error)}`);
|
|
1824
1861
|
return "read-failed";
|
|
1825
1862
|
}
|
|
1826
1863
|
const contentType = sniffContentType(bytes) ?? meta.contentType;
|
|
1827
1864
|
if (!contentType) {
|
|
1828
|
-
this.log("warning", "
|
|
1865
|
+
this.log("warning", "imagem descartada: formato n\xE3o reconhecido (aceitos: PNG, JPEG, WEBP)");
|
|
1829
1866
|
return "unsupported-format";
|
|
1830
1867
|
}
|
|
1831
1868
|
if (bytes.byteLength > MAX_ATTACHMENT_BYTES) {
|
|
1832
1869
|
this.log(
|
|
1833
1870
|
"warning",
|
|
1834
|
-
`
|
|
1871
|
+
`imagem descartada: ${Math.round(bytes.byteLength / 1024)} KB excede o limite de ${MAX_ATTACHMENT_BYTES / 1024 / 1024} MB`
|
|
1835
1872
|
);
|
|
1836
1873
|
return "too-large";
|
|
1837
1874
|
}
|
|
@@ -1862,11 +1899,11 @@ var RunReporter = class {
|
|
|
1862
1899
|
return "unsupported";
|
|
1863
1900
|
}
|
|
1864
1901
|
if (error instanceof TasksApiError && error.status === 429) {
|
|
1865
|
-
this.log("warning", "
|
|
1902
|
+
this.log("warning", "imagem descartada: limite de anexos por execu\xE7\xE3o atingido no servidor");
|
|
1866
1903
|
return "quota";
|
|
1867
1904
|
}
|
|
1868
1905
|
if (this.abortController.signal.aborted) return "canceled";
|
|
1869
|
-
this.log("warning", `falha ao enviar
|
|
1906
|
+
this.log("warning", `falha ao enviar a imagem: ${String(error)}`);
|
|
1870
1907
|
void this.api.confirmAttachment(this.runId, id, { status: "failed" }).catch(() => {
|
|
1871
1908
|
});
|
|
1872
1909
|
return "upload-failed";
|
|
@@ -2030,23 +2067,39 @@ var Step = class {
|
|
|
2030
2067
|
/**
|
|
2031
2068
|
* Desfecho da step. `message` (opcional) é o motivo legível — vai em `data.message`
|
|
2032
2069
|
* e aparece na Central (performance e banner da execução) e no log de FINISH.
|
|
2070
|
+
* `options.image` pendura um print do estado da tela em `data.attachments`.
|
|
2033
2071
|
*/
|
|
2034
|
-
setStatus(status, message) {
|
|
2072
|
+
setStatus(status, message, options) {
|
|
2035
2073
|
this.status = status;
|
|
2036
2074
|
if (message) this.setData("message", stepMessage(message));
|
|
2075
|
+
if (options?.image !== void 0) this.attachImage(options.image);
|
|
2037
2076
|
return this;
|
|
2038
2077
|
}
|
|
2039
2078
|
/**
|
|
2040
2079
|
* Marca a step como `error` guardando o motivo em `data.error` ({ type, message }).
|
|
2041
2080
|
* Aceita qualquer valor lançado; `message` sobrescreve a mensagem do erro.
|
|
2081
|
+
* `options.image` pendura um print do estado da tela em `data.attachments`.
|
|
2042
2082
|
*/
|
|
2043
|
-
setError(error, message) {
|
|
2083
|
+
setError(error, message, options) {
|
|
2044
2084
|
this.status = "error";
|
|
2045
2085
|
const type = error instanceof Error ? error.name : typeof error === "object" && error !== null ? "Error" : void 0;
|
|
2046
2086
|
const raw = message ?? (error instanceof Error ? error.message || error.name : String(error));
|
|
2047
2087
|
this.setData("error", { ...type ? { type } : {}, message: stepMessage(raw) });
|
|
2088
|
+
if (options?.image !== void 0) this.attachImage(options.image);
|
|
2048
2089
|
return this;
|
|
2049
2090
|
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Dispara o upload pela run (sink do RunReporter) e referencia o anexo em
|
|
2093
|
+
* `data.attachments` (chave reservada; teto igual ao das linhas de log). Fora de uma
|
|
2094
|
+
* run não há onde pendurar a imagem — descarta com aviso único.
|
|
2095
|
+
*/
|
|
2096
|
+
attachImage(image3) {
|
|
2097
|
+
const current = Array.isArray(this.data?.attachments) ? this.data.attachments : [];
|
|
2098
|
+
if (current.length >= MAX_ATTACHMENTS_PER_LOG_ENTRY) return;
|
|
2099
|
+
const id = this.transaction.attachImage(image3);
|
|
2100
|
+
if (!id) return;
|
|
2101
|
+
this.setData("attachments", [...current, id]);
|
|
2102
|
+
}
|
|
2050
2103
|
/** Motivo registrado via setStatus(…, message) ou setError — usado no log de FINISH. */
|
|
2051
2104
|
reason() {
|
|
2052
2105
|
const error = this.data?.error;
|
|
@@ -2107,6 +2160,8 @@ var Transaction = class {
|
|
|
2107
2160
|
tags = {};
|
|
2108
2161
|
finished = false;
|
|
2109
2162
|
lifecycleSink;
|
|
2163
|
+
attachmentSink;
|
|
2164
|
+
warnedNoAttachmentSink = false;
|
|
2110
2165
|
// instalado pelo RunReporter: abrir uma step é um ponto de parada cooperativo (lança RunCanceled)
|
|
2111
2166
|
stepGuard;
|
|
2112
2167
|
// eventos emitidos antes do sink existir (o START da transaction sai no construtor,
|
|
@@ -2137,6 +2192,19 @@ var Transaction = class {
|
|
|
2137
2192
|
this.lifecycleBacklog = [];
|
|
2138
2193
|
for (const [level, message] of backlog) sink(level, message);
|
|
2139
2194
|
}
|
|
2195
|
+
/** Conecta o pipeline de anexos da run — é o que dá imagem às steps. */
|
|
2196
|
+
setAttachmentSink(sink) {
|
|
2197
|
+
this.attachmentSink = sink;
|
|
2198
|
+
}
|
|
2199
|
+
/** Imagem de uma step → id do anexo pela run; sem run, descarta com aviso único. */
|
|
2200
|
+
attachImage(image3) {
|
|
2201
|
+
if (this.attachmentSink) return this.attachmentSink(image3, nowISO());
|
|
2202
|
+
if (!this.warnedNoAttachmentSink) {
|
|
2203
|
+
this.warnedNoAttachmentSink = true;
|
|
2204
|
+
console.warn("[hobots] imagem de step descartada \u2014 s\xF3 \xE9 suportada dentro de uma execu\xE7\xE3o");
|
|
2205
|
+
}
|
|
2206
|
+
return void 0;
|
|
2207
|
+
}
|
|
2140
2208
|
/**
|
|
2141
2209
|
* Conecta a checagem de cancelamento chamada antes de cada step nova — é assim que
|
|
2142
2210
|
* `hobots.transaction.startChild()` (e `step.startChild()`) param uma run cancelada.
|
|
@@ -2199,13 +2267,12 @@ var Transaction = class {
|
|
|
2199
2267
|
// src/tasks/runner.ts
|
|
2200
2268
|
var DEFAULT_RETRY_DELAY_MS = 5e3;
|
|
2201
2269
|
async function executeRun(input) {
|
|
2202
|
-
const { run,
|
|
2203
|
-
const options =
|
|
2204
|
-
const tx = new Transaction(client,
|
|
2270
|
+
const { run, handler, api, client, logger, tracker } = input;
|
|
2271
|
+
const options = handler.options;
|
|
2272
|
+
const tx = new Transaction(client, "run", "hobots.run", run.id);
|
|
2205
2273
|
const reporter = new RunReporter(
|
|
2206
2274
|
api,
|
|
2207
2275
|
run.id,
|
|
2208
|
-
run.task,
|
|
2209
2276
|
logger,
|
|
2210
2277
|
tx,
|
|
2211
2278
|
options.logFlushIntervalMs,
|
|
@@ -2221,9 +2288,9 @@ async function executeRun(input) {
|
|
|
2221
2288
|
tx.finish();
|
|
2222
2289
|
return;
|
|
2223
2290
|
}
|
|
2224
|
-
logger.debug(`executando run ${run.id}
|
|
2291
|
+
logger.debug(`executando run ${run.id}`);
|
|
2225
2292
|
try {
|
|
2226
|
-
await runWithRunContext({ reporter }, () => invokeWithRetry(
|
|
2293
|
+
await runWithRunContext({ reporter }, () => invokeWithRetry(handler, run, reporter, logger));
|
|
2227
2294
|
await reporter.finalizeAttachments();
|
|
2228
2295
|
await reporter.finalizeItems();
|
|
2229
2296
|
warnIncompleteProgress(reporter);
|
|
@@ -2251,11 +2318,11 @@ function warnIncompleteProgress(reporter) {
|
|
|
2251
2318
|
`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`
|
|
2252
2319
|
);
|
|
2253
2320
|
}
|
|
2254
|
-
function invokeWithRetry(
|
|
2255
|
-
const { retries, retryDelayMs } =
|
|
2321
|
+
function invokeWithRetry(agent, run, reporter, logger) {
|
|
2322
|
+
const { retries, retryDelayMs } = agent.options;
|
|
2256
2323
|
const total = retries + 1;
|
|
2257
2324
|
const ctx = reporter.context;
|
|
2258
|
-
return retryWithBackoff(() => invokeWithTimeout(
|
|
2325
|
+
return retryWithBackoff(() => invokeWithTimeout(agent, run, reporter, logger), {
|
|
2259
2326
|
retries,
|
|
2260
2327
|
delayMs: (attempt) => resolveDelay(retryDelayMs, attempt, DEFAULT_RETRY_DELAY_MS),
|
|
2261
2328
|
shouldRetry: () => !reporter.isDead,
|
|
@@ -2269,9 +2336,9 @@ function invokeWithRetry(task, run, reporter, logger) {
|
|
|
2269
2336
|
}
|
|
2270
2337
|
var TIMEOUT_SENTINEL = /* @__PURE__ */ Symbol("task-timeout");
|
|
2271
2338
|
var CANCEL_SENTINEL = /* @__PURE__ */ Symbol("task-canceled");
|
|
2272
|
-
async function invokeWithTimeout(
|
|
2273
|
-
const { timeoutMs, cancelGraceMs } =
|
|
2274
|
-
const handler =
|
|
2339
|
+
async function invokeWithTimeout(agent, run, reporter, logger) {
|
|
2340
|
+
const { timeoutMs, cancelGraceMs } = agent.options;
|
|
2341
|
+
const handler = agent.handler;
|
|
2275
2342
|
const invocation = Promise.resolve().then(() => handler(run.params, reporter.context));
|
|
2276
2343
|
const races = [];
|
|
2277
2344
|
if (timeoutMs && timeoutMs > 0) {
|
|
@@ -2328,7 +2395,7 @@ var TaskAgent = class {
|
|
|
2328
2395
|
this.logger = options.logger;
|
|
2329
2396
|
this.api = options.api;
|
|
2330
2397
|
this.client = options.client;
|
|
2331
|
-
this.
|
|
2398
|
+
this.handler = options.handler;
|
|
2332
2399
|
this.tracker = options.tracker;
|
|
2333
2400
|
this.pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
|
|
2334
2401
|
this.backoff = new Backoff(this.logger, "poll", this.pollIntervalMs);
|
|
@@ -2338,7 +2405,7 @@ var TaskAgent = class {
|
|
|
2338
2405
|
pollIntervalMs;
|
|
2339
2406
|
api;
|
|
2340
2407
|
client;
|
|
2341
|
-
|
|
2408
|
+
handler;
|
|
2342
2409
|
tracker;
|
|
2343
2410
|
backoff;
|
|
2344
2411
|
state = "idle";
|
|
@@ -2354,10 +2421,10 @@ var TaskAgent = class {
|
|
|
2354
2421
|
throw new Error("[hobots] start do agente durante o stop \u2014 aguarde o stop terminar");
|
|
2355
2422
|
}
|
|
2356
2423
|
this.state = "started";
|
|
2357
|
-
this.logger.debug(`polling iniciado (
|
|
2424
|
+
this.logger.debug(`polling iniciado (concorr\xEAncia ${this.handler.options.concurrency})`);
|
|
2358
2425
|
this.schedule(0);
|
|
2359
2426
|
}
|
|
2360
|
-
/**
|
|
2427
|
+
/** Antecipa o próximo poll para já. */
|
|
2361
2428
|
wake() {
|
|
2362
2429
|
if (this.state !== "started") return;
|
|
2363
2430
|
if (this.timer) {
|
|
@@ -2388,21 +2455,14 @@ var TaskAgent = class {
|
|
|
2388
2455
|
void this.tick();
|
|
2389
2456
|
}, delayMs);
|
|
2390
2457
|
}
|
|
2391
|
-
/** Slots livres
|
|
2458
|
+
/** Slots livres do agente — vira o `max` do poll (nunca reivindica trabalho para deixar parado). */
|
|
2392
2459
|
freeSlots() {
|
|
2393
|
-
const
|
|
2394
|
-
|
|
2395
|
-
for (const task of this.registry.remoteTasks()) {
|
|
2396
|
-
const free = task.options.concurrency - this.tracker.activeFor(task.slug);
|
|
2397
|
-
if (free <= 0) continue;
|
|
2398
|
-
tasks.push(task.slug);
|
|
2399
|
-
max += free;
|
|
2400
|
-
}
|
|
2401
|
-
return { tasks, max: Math.min(MAX_CLAIM_PER_POLL, max) };
|
|
2460
|
+
const free = this.handler.options.concurrency - this.tracker.activeCount;
|
|
2461
|
+
return Math.min(MAX_CLAIM_PER_POLL, Math.max(0, free));
|
|
2402
2462
|
}
|
|
2403
2463
|
async tick() {
|
|
2404
2464
|
if (this.state !== "started") return;
|
|
2405
|
-
const
|
|
2465
|
+
const max = this.freeSlots();
|
|
2406
2466
|
if (max === 0) {
|
|
2407
2467
|
this.schedule(this.pollIntervalMs);
|
|
2408
2468
|
return;
|
|
@@ -2412,11 +2472,9 @@ var TaskAgent = class {
|
|
|
2412
2472
|
const response = await this.api.poll(
|
|
2413
2473
|
{
|
|
2414
2474
|
// identidade canônica do agente no painel — o mesmo instanceId do heartbeat,
|
|
2415
|
-
// então os dois canais convergem em um agente
|
|
2416
|
-
// tasks (register + start); o filtro `tasks` abaixo só as que este poll pode rodar.
|
|
2475
|
+
// então os dois canais convergem em um agente
|
|
2417
2476
|
instance: this.options.instanceInfo(),
|
|
2418
|
-
max
|
|
2419
|
-
tasks
|
|
2477
|
+
max
|
|
2420
2478
|
},
|
|
2421
2479
|
this.pollAbort.signal
|
|
2422
2480
|
);
|
|
@@ -2440,17 +2498,7 @@ var TaskAgent = class {
|
|
|
2440
2498
|
this.activeRuns.add(promise);
|
|
2441
2499
|
}
|
|
2442
2500
|
async executeClaimed(run) {
|
|
2443
|
-
|
|
2444
|
-
if (!task || task.mode !== "remote") {
|
|
2445
|
-
const tx = new Transaction(this.client, `run:${run.task}`, "hobots.task", run.id);
|
|
2446
|
-
const reporter = new RunReporter(this.api, run.id, run.task, this.logger, tx);
|
|
2447
|
-
reporter.reportFailure(toRunError(Object.assign(new Error(`tarefa n\xE3o registrada no agente: ${run.task}`), { name: "HandlerNotFound" })));
|
|
2448
|
-
await reporter.finalize();
|
|
2449
|
-
tx.setStatus("error");
|
|
2450
|
-
tx.finish();
|
|
2451
|
-
return;
|
|
2452
|
-
}
|
|
2453
|
-
await executeRun({ run, task, api: this.api, client: this.client, logger: this.logger, tracker: this.tracker });
|
|
2501
|
+
await executeRun({ run, handler: this.handler, api: this.api, client: this.client, logger: this.logger, tracker: this.tracker });
|
|
2454
2502
|
}
|
|
2455
2503
|
};
|
|
2456
2504
|
|
|
@@ -2458,11 +2506,11 @@ var TaskAgent = class {
|
|
|
2458
2506
|
var KEY_RE = /^[a-zA-Z0-9_.-]{1,100}$/;
|
|
2459
2507
|
var MAX_COLUMNS = WIRE_LIMITS.MAX_ITEM_COLUMNS;
|
|
2460
2508
|
var MAX_LABEL_CHARS = WIRE_LIMITS.MAX_ITEM_COLUMN_LABEL_CHARS;
|
|
2461
|
-
function normalizeItemColumns(
|
|
2509
|
+
function normalizeItemColumns(input, logger) {
|
|
2462
2510
|
if (input === void 0 || input === null) return void 0;
|
|
2463
2511
|
const columns = input.columns;
|
|
2464
2512
|
if (typeof input !== "object" || !Array.isArray(columns)) {
|
|
2465
|
-
logger.warn(`
|
|
2513
|
+
logger.warn(`items.columns precisa ser uma lista \u2014 declara\xE7\xE3o de colunas ignorada`);
|
|
2466
2514
|
return void 0;
|
|
2467
2515
|
}
|
|
2468
2516
|
const result = [];
|
|
@@ -2472,15 +2520,15 @@ function normalizeItemColumns(slug, input, logger) {
|
|
|
2472
2520
|
const rawLabel = typeof entry === "object" && entry !== null ? entry.label : void 0;
|
|
2473
2521
|
const key = typeof rawKey === "string" ? rawKey.trim() : "";
|
|
2474
2522
|
if (!KEY_RE.test(key)) {
|
|
2475
|
-
logger.warn(`
|
|
2523
|
+
logger.warn(`coluna de item inv\xE1lida ignorada (${JSON.stringify(rawKey)}) \u2014 use letras, n\xFAmeros, ponto, h\xEDfen e underline`);
|
|
2476
2524
|
continue;
|
|
2477
2525
|
}
|
|
2478
2526
|
if (seen.has(key)) {
|
|
2479
|
-
logger.warn(`
|
|
2527
|
+
logger.warn(`coluna de item "${key}" repetida ignorada`);
|
|
2480
2528
|
continue;
|
|
2481
2529
|
}
|
|
2482
2530
|
if (result.length >= MAX_COLUMNS) {
|
|
2483
|
-
logger.warn(`
|
|
2531
|
+
logger.warn(`no m\xE1ximo ${MAX_COLUMNS} colunas de item \u2014 "${key}" em diante ignoradas`);
|
|
2484
2532
|
break;
|
|
2485
2533
|
}
|
|
2486
2534
|
seen.add(key);
|
|
@@ -2492,15 +2540,15 @@ function normalizeItemColumns(slug, input, logger) {
|
|
|
2492
2540
|
|
|
2493
2541
|
// src/tasks/local.ts
|
|
2494
2542
|
var LocalRunner = class {
|
|
2495
|
-
constructor(
|
|
2496
|
-
this.
|
|
2543
|
+
constructor(handler, deps) {
|
|
2544
|
+
this.handler = handler;
|
|
2497
2545
|
this.deps = deps;
|
|
2498
|
-
this.backoff = new Backoff(deps.logger,
|
|
2546
|
+
this.backoff = new Backoff(deps.logger, "cria\xE7\xE3o da run");
|
|
2499
2547
|
this.done = new Promise((resolve2) => {
|
|
2500
2548
|
this.resolveDone = resolve2;
|
|
2501
2549
|
});
|
|
2502
2550
|
}
|
|
2503
|
-
|
|
2551
|
+
handler;
|
|
2504
2552
|
deps;
|
|
2505
2553
|
abort = new AbortController();
|
|
2506
2554
|
backoff;
|
|
@@ -2511,17 +2559,17 @@ var LocalRunner = class {
|
|
|
2511
2559
|
try {
|
|
2512
2560
|
const run = await this.createRun();
|
|
2513
2561
|
if (!run) return;
|
|
2514
|
-
this.deps.logger.debug(`run ${run.id} aberta
|
|
2562
|
+
this.deps.logger.debug(`run ${run.id} aberta (modo start)`);
|
|
2515
2563
|
await executeRun({
|
|
2516
2564
|
run,
|
|
2517
|
-
|
|
2565
|
+
handler: this.handler,
|
|
2518
2566
|
api: this.deps.api,
|
|
2519
2567
|
client: this.deps.client,
|
|
2520
2568
|
logger: this.deps.logger,
|
|
2521
2569
|
tracker: this.deps.tracker
|
|
2522
2570
|
});
|
|
2523
2571
|
} catch (error) {
|
|
2524
|
-
this.deps.logger.error(
|
|
2572
|
+
this.deps.logger.error("erro inesperado na execu\xE7\xE3o do agente:", error);
|
|
2525
2573
|
} finally {
|
|
2526
2574
|
this.resolveDone();
|
|
2527
2575
|
}
|
|
@@ -2534,7 +2582,7 @@ var LocalRunner = class {
|
|
|
2534
2582
|
for (; ; ) {
|
|
2535
2583
|
if (this.abort.signal.aborted) return void 0;
|
|
2536
2584
|
try {
|
|
2537
|
-
return await this.deps.api.createRun({
|
|
2585
|
+
return await this.deps.api.createRun({ instance: this.deps.instanceInfo() }, this.abort.signal);
|
|
2538
2586
|
} catch (error) {
|
|
2539
2587
|
if (this.abort.signal.aborted) return void 0;
|
|
2540
2588
|
await abortableSleep(this.backoff.next(error), this.abort.signal);
|
|
@@ -2544,45 +2592,6 @@ var LocalRunner = class {
|
|
|
2544
2592
|
};
|
|
2545
2593
|
|
|
2546
2594
|
// src/tasks/registry.ts
|
|
2547
|
-
var TaskRegistry = class {
|
|
2548
|
-
tasks = /* @__PURE__ */ new Map();
|
|
2549
|
-
add(task) {
|
|
2550
|
-
if (this.tasks.has(task.slug)) {
|
|
2551
|
-
throw new Error(`[hobots] task "${task.slug}" j\xE1 registrada \u2014 cada slug s\xF3 pode ter um handler (register ou start)`);
|
|
2552
|
-
}
|
|
2553
|
-
this.tasks.set(task.slug, task);
|
|
2554
|
-
}
|
|
2555
|
-
get(slug) {
|
|
2556
|
-
return this.tasks.get(slug);
|
|
2557
|
-
}
|
|
2558
|
-
get size() {
|
|
2559
|
-
return this.tasks.size;
|
|
2560
|
-
}
|
|
2561
|
-
/** Tasks que chegam pelo poll (register). */
|
|
2562
|
-
remoteTasks() {
|
|
2563
|
-
return [...this.tasks.values()].filter((task) => task.mode === "remote");
|
|
2564
|
-
}
|
|
2565
|
-
/** Tasks abertas pelo próprio bot (start). */
|
|
2566
|
-
localTasks() {
|
|
2567
|
-
return [...this.tasks.values()].filter((task) => task.mode === "local");
|
|
2568
|
-
}
|
|
2569
|
-
/** Todos os slugs, para a presença da instância (`instance.tasks`). */
|
|
2570
|
-
allSlugs() {
|
|
2571
|
-
return [...this.tasks.keys()];
|
|
2572
|
-
}
|
|
2573
|
-
/** Colunas de itens declaradas no código, por slug (`instance.task_items`); undefined se nenhuma task declarou. */
|
|
2574
|
-
itemDeclarations() {
|
|
2575
|
-
let result;
|
|
2576
|
-
for (const task of this.tasks.values()) {
|
|
2577
|
-
if (!task.options.itemColumns) continue;
|
|
2578
|
-
(result ??= {})[task.slug] = { attributes: task.options.itemColumns };
|
|
2579
|
-
}
|
|
2580
|
-
return result;
|
|
2581
|
-
}
|
|
2582
|
-
clear() {
|
|
2583
|
-
this.tasks.clear();
|
|
2584
|
-
}
|
|
2585
|
-
};
|
|
2586
2595
|
var RunTracker = class {
|
|
2587
2596
|
constructor(api) {
|
|
2588
2597
|
this.api = api;
|
|
@@ -2603,12 +2612,6 @@ var RunTracker = class {
|
|
|
2603
2612
|
get activeCount() {
|
|
2604
2613
|
return this.reporters.size;
|
|
2605
2614
|
}
|
|
2606
|
-
/** Runs ativas de uma task (concorrência por task no poll). */
|
|
2607
|
-
activeFor(slug) {
|
|
2608
|
-
let count = 0;
|
|
2609
|
-
for (const reporter of this.reporters) if (reporter.context.task === slug) count++;
|
|
2610
|
-
return count;
|
|
2611
|
-
}
|
|
2612
2615
|
/** A única run ativa, quando há exatamente uma — o que `hobots.*` usa fora de um contexto async. */
|
|
2613
2616
|
singleActive() {
|
|
2614
2617
|
if (this.reporters.size !== 1) return void 0;
|
|
@@ -2697,40 +2700,13 @@ var DEFAULT_RETRIES2 = 1;
|
|
|
2697
2700
|
var DEFAULT_RETRY_DELAY_MS2 = 5e3;
|
|
2698
2701
|
var DEFAULT_CANCEL_GRACE_MS = 1e4;
|
|
2699
2702
|
var MAX_CONCURRENCY = 5;
|
|
2700
|
-
var MAX_SLUG_CHARS = 255;
|
|
2701
|
-
var PROCESS_LEVEL_KEYS = [
|
|
2702
|
-
"clientSecret",
|
|
2703
|
-
"instanceId",
|
|
2704
|
-
"environment",
|
|
2705
|
-
"release",
|
|
2706
|
-
"tags",
|
|
2707
|
-
"debug",
|
|
2708
|
-
"machineMetrics",
|
|
2709
|
-
"captureUnhandled",
|
|
2710
|
-
"installExitHandlers"
|
|
2711
|
-
];
|
|
2712
2703
|
var runtime;
|
|
2713
2704
|
var warnedNoRuntime = false;
|
|
2714
2705
|
function hostname() {
|
|
2715
2706
|
if (!isNode()) return void 0;
|
|
2716
2707
|
return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
|
|
2717
2708
|
}
|
|
2718
|
-
function
|
|
2719
|
-
if (a === b) return true;
|
|
2720
|
-
if (a && b && typeof a === "object" && typeof b === "object") return JSON.stringify(a) === JSON.stringify(b);
|
|
2721
|
-
return false;
|
|
2722
|
-
}
|
|
2723
|
-
function ensureRuntime(options) {
|
|
2724
|
-
if (runtime) {
|
|
2725
|
-
for (const key of PROCESS_LEVEL_KEYS) {
|
|
2726
|
-
if (options[key] !== void 0 && !sameValue(options[key], runtime.options[key])) {
|
|
2727
|
-
runtime.logger.warn(
|
|
2728
|
-
`op\xE7\xE3o "${key}" divergente ignorada \u2014 o primeiro register()/start() define as op\xE7\xF5es do processo`
|
|
2729
|
-
);
|
|
2730
|
-
}
|
|
2731
|
-
}
|
|
2732
|
-
return runtime;
|
|
2733
|
-
}
|
|
2709
|
+
function createRuntime(options, handler) {
|
|
2734
2710
|
const config = resolveConfig({ clientSecret: options.clientSecret });
|
|
2735
2711
|
const logger = makeLogger(options.debug ?? false);
|
|
2736
2712
|
const environment = resolveEnvironmentOption(options.environment);
|
|
@@ -2738,19 +2714,19 @@ function ensureRuntime(options) {
|
|
|
2738
2714
|
{ environment, release: options.release, tags: options.tags, debug: options.debug },
|
|
2739
2715
|
config
|
|
2740
2716
|
);
|
|
2741
|
-
const api = new TasksApi({ baseUrl: config.
|
|
2742
|
-
|
|
2717
|
+
const api = new TasksApi({ baseUrl: config.runsBaseUrl, key: config.key, logger });
|
|
2718
|
+
const rt = {
|
|
2743
2719
|
options: { ...options, clientSecret: void 0 },
|
|
2744
2720
|
config,
|
|
2745
2721
|
client,
|
|
2746
2722
|
logger,
|
|
2747
2723
|
api,
|
|
2748
|
-
|
|
2724
|
+
handler,
|
|
2749
2725
|
tracker: new RunTracker(api),
|
|
2750
2726
|
sampler: void 0,
|
|
2751
2727
|
heartbeat: void 0,
|
|
2752
2728
|
agent: void 0,
|
|
2753
|
-
|
|
2729
|
+
local: void 0,
|
|
2754
2730
|
instanceId: resolveInstanceId(options.instanceId),
|
|
2755
2731
|
environment,
|
|
2756
2732
|
pollIntervalMs: void 0,
|
|
@@ -2760,24 +2736,19 @@ function ensureRuntime(options) {
|
|
|
2760
2736
|
uninstallGlobalHandlers: options.captureUnhandled !== false ? installGlobalHandlers(client) : void 0,
|
|
2761
2737
|
uninstallShutdown: void 0
|
|
2762
2738
|
};
|
|
2763
|
-
runtime.options.clientSecret = options.clientSecret;
|
|
2764
2739
|
logger.debug("inicializado", { endpoint: config.ingestEndpoint });
|
|
2765
|
-
return
|
|
2740
|
+
return rt;
|
|
2766
2741
|
}
|
|
2767
|
-
function
|
|
2768
|
-
if (typeof slug !== "string" || slug.trim().length === 0 || slug.length > MAX_SLUG_CHARS) {
|
|
2769
|
-
throw new Error(`[hobots] ${fn}(): o slug da task precisa ser uma string de 1 a ${MAX_SLUG_CHARS} caracteres`);
|
|
2770
|
-
}
|
|
2742
|
+
function validateHandler(handler, fn) {
|
|
2771
2743
|
if (typeof handler !== "function") {
|
|
2772
|
-
throw new Error(`[hobots] ${fn}(
|
|
2744
|
+
throw new Error(`[hobots] ${fn}(): o handler precisa ser uma fun\xE7\xE3o \u2014 ${fn}(async (params) => { ... })`);
|
|
2773
2745
|
}
|
|
2774
|
-
return slug.trim();
|
|
2775
2746
|
}
|
|
2776
2747
|
function clampConcurrency(value) {
|
|
2777
2748
|
if (value === void 0 || !Number.isFinite(value)) return 1;
|
|
2778
2749
|
return Math.min(MAX_CONCURRENCY, Math.max(1, Math.floor(value)));
|
|
2779
2750
|
}
|
|
2780
|
-
function
|
|
2751
|
+
function resolveRunOptions(options, mode, logger) {
|
|
2781
2752
|
const retries = options.retries === void 0 || !Number.isFinite(options.retries) ? DEFAULT_RETRIES2 : Math.max(0, Math.floor(options.retries));
|
|
2782
2753
|
return {
|
|
2783
2754
|
retries,
|
|
@@ -2787,7 +2758,7 @@ function resolveTaskOptions(slug, options, mode, logger) {
|
|
|
2787
2758
|
logFlushIntervalMs: options.logFlushIntervalMs,
|
|
2788
2759
|
cancelCheckIntervalMs: options.cancelCheckIntervalMs,
|
|
2789
2760
|
cancelGraceMs: options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS,
|
|
2790
|
-
itemColumns: normalizeItemColumns(
|
|
2761
|
+
itemColumns: normalizeItemColumns(options.items, logger)
|
|
2791
2762
|
};
|
|
2792
2763
|
}
|
|
2793
2764
|
function instanceInfo(rt) {
|
|
@@ -2797,10 +2768,10 @@ function instanceInfo(rt) {
|
|
|
2797
2768
|
host: hostname(),
|
|
2798
2769
|
// ambiente da instância: junto do heartbeat, é como o servidor sabe onde o robô roda
|
|
2799
2770
|
environment: rt.environment,
|
|
2800
|
-
//
|
|
2801
|
-
|
|
2771
|
+
// como esta instância trabalha — o painel mostra "sob demanda" / "sempre ligado"
|
|
2772
|
+
mode: rt.handler.mode === "remote" ? "on_demand" : "always_on",
|
|
2802
2773
|
// colunas da aba "itens" declaradas no código — a plataforma usa enquanto o usuário não personalizar
|
|
2803
|
-
|
|
2774
|
+
items: rt.handler.options.itemColumns ? { attributes: rt.handler.options.itemColumns } : void 0,
|
|
2804
2775
|
// o servidor calcula a tolerância de offline (3×) a partir disto em agentes que fazem
|
|
2805
2776
|
// poll; clampado à faixa aceita pelo schema (1s..15min)
|
|
2806
2777
|
poll_interval_ms: rt.agent ? Math.min(Math.max(rt.agent.pollIntervalMs, 1e3), 9e5) : void 0,
|
|
@@ -2813,22 +2784,22 @@ function ensureAgent(rt) {
|
|
|
2813
2784
|
api: rt.api,
|
|
2814
2785
|
client: rt.client,
|
|
2815
2786
|
logger: rt.logger,
|
|
2816
|
-
|
|
2787
|
+
handler: rt.handler,
|
|
2817
2788
|
tracker: rt.tracker,
|
|
2818
2789
|
instanceInfo: () => instanceInfo(rt),
|
|
2819
2790
|
pollIntervalMs: rt.pollIntervalMs
|
|
2820
2791
|
});
|
|
2821
2792
|
return rt.agent;
|
|
2822
2793
|
}
|
|
2823
|
-
function launchLocal(rt
|
|
2824
|
-
const runner = new LocalRunner(
|
|
2794
|
+
function launchLocal(rt) {
|
|
2795
|
+
const runner = new LocalRunner(rt.handler, {
|
|
2825
2796
|
api: rt.api,
|
|
2826
2797
|
client: rt.client,
|
|
2827
2798
|
logger: rt.logger,
|
|
2828
2799
|
tracker: rt.tracker,
|
|
2829
2800
|
instanceInfo: () => instanceInfo(rt)
|
|
2830
2801
|
});
|
|
2831
|
-
rt.
|
|
2802
|
+
rt.local = runner;
|
|
2832
2803
|
void runner.run();
|
|
2833
2804
|
}
|
|
2834
2805
|
async function boot(rt) {
|
|
@@ -2850,8 +2821,8 @@ async function boot(rt) {
|
|
|
2850
2821
|
if (rt.options.installExitHandlers !== false) {
|
|
2851
2822
|
rt.uninstallShutdown = installShutdownHandlers(rt.tracker);
|
|
2852
2823
|
}
|
|
2853
|
-
if (rt.
|
|
2854
|
-
|
|
2824
|
+
if (rt.handler.mode === "remote") ensureAgent(rt).start();
|
|
2825
|
+
else launchLocal(rt);
|
|
2855
2826
|
}
|
|
2856
2827
|
function scheduleBoot(rt) {
|
|
2857
2828
|
if (rt.bootScheduled) return;
|
|
@@ -2860,34 +2831,23 @@ function scheduleBoot(rt) {
|
|
|
2860
2831
|
rt.bootPromise = boot(rt).catch((error) => rt.logger.error("falha ao iniciar o SDK:", error));
|
|
2861
2832
|
});
|
|
2862
2833
|
}
|
|
2863
|
-
function
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
if (rt.pollIntervalMs === void 0 && !rt.agent) rt.pollIntervalMs = options.pollIntervalMs;
|
|
2868
|
-
else if (rt.pollIntervalMs !== options.pollIntervalMs) {
|
|
2869
|
-
rt.logger.warn('op\xE7\xE3o "pollIntervalMs" divergente ignorada \u2014 o primeiro register() define o intervalo do poll');
|
|
2870
|
-
}
|
|
2871
|
-
}
|
|
2872
|
-
const registered = { slug, mode, handler, options: resolveTaskOptions(slug, options, mode, rt.logger) };
|
|
2873
|
-
rt.registry.add(registered);
|
|
2874
|
-
if (!rt.booted) {
|
|
2875
|
-
scheduleBoot(rt);
|
|
2876
|
-
return;
|
|
2877
|
-
}
|
|
2878
|
-
if (mode === "remote") {
|
|
2879
|
-
const agent = ensureAgent(rt);
|
|
2880
|
-
agent.start();
|
|
2881
|
-
agent.wake();
|
|
2882
|
-
} else {
|
|
2883
|
-
launchLocal(rt, registered);
|
|
2834
|
+
function startAgent(fn, mode, handler, options) {
|
|
2835
|
+
validateHandler(handler, fn);
|
|
2836
|
+
if (runtime) {
|
|
2837
|
+
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)`);
|
|
2884
2838
|
}
|
|
2839
|
+
const logger = makeLogger(options.debug ?? false);
|
|
2840
|
+
const agentHandler = { mode, handler, options: resolveRunOptions(options, mode, logger) };
|
|
2841
|
+
const rt = createRuntime(options, agentHandler);
|
|
2842
|
+
if (mode === "remote" && options.pollIntervalMs !== void 0) rt.pollIntervalMs = options.pollIntervalMs;
|
|
2843
|
+
runtime = rt;
|
|
2844
|
+
scheduleBoot(rt);
|
|
2885
2845
|
}
|
|
2886
|
-
function register(
|
|
2887
|
-
|
|
2846
|
+
function register(handler, options = {}) {
|
|
2847
|
+
startAgent("register", "remote", handler, options);
|
|
2888
2848
|
}
|
|
2889
|
-
function start(
|
|
2890
|
-
|
|
2849
|
+
function start(handler, options = {}) {
|
|
2850
|
+
startAgent("start", "local", handler, options);
|
|
2891
2851
|
}
|
|
2892
2852
|
function currentReporter() {
|
|
2893
2853
|
return currentRunReporter() ?? runtime?.tracker.singleActive();
|
|
@@ -2901,23 +2861,18 @@ function toRunLogLevel(type) {
|
|
|
2901
2861
|
function forwardAttachment(rt, attachment, message, type) {
|
|
2902
2862
|
const reporter = currentReporter();
|
|
2903
2863
|
if (!reporter) {
|
|
2904
|
-
rt.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o
|
|
2864
|
+
rt.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o");
|
|
2905
2865
|
return;
|
|
2906
2866
|
}
|
|
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
|
-
});
|
|
2867
|
+
const withMeta = typeof attachment === "object" && !isImageAttachment(attachment) && "image" in attachment ? image(attachment.image, { caption: attachment.caption, contentType: attachment.contentType }) : attachment;
|
|
2868
|
+
reporter.log(toRunLogLevel(type), message, withMeta);
|
|
2914
2869
|
}
|
|
2915
2870
|
function captureException(error, hint) {
|
|
2916
2871
|
const rt = runtime;
|
|
2917
2872
|
if (!rt) {
|
|
2918
2873
|
if (!warnedNoRuntime) {
|
|
2919
2874
|
warnedNoRuntime = true;
|
|
2920
|
-
console.warn("[hobots] captureException antes de
|
|
2875
|
+
console.warn("[hobots] captureException antes de iniciar o agente (register/start) \u2014 evento descartado");
|
|
2921
2876
|
}
|
|
2922
2877
|
return void 0;
|
|
2923
2878
|
}
|
|
@@ -2933,7 +2888,7 @@ async function close(timeoutMs = 3e4) {
|
|
|
2933
2888
|
runtime = void 0;
|
|
2934
2889
|
await rt.bootPromise;
|
|
2935
2890
|
if (rt.agent) await rt.agent.stop(0);
|
|
2936
|
-
|
|
2891
|
+
rt.local?.stop();
|
|
2937
2892
|
const idle = await rt.tracker.waitIdle(timeoutMs);
|
|
2938
2893
|
if (!idle) {
|
|
2939
2894
|
rt.logger.warn(`${rt.tracker.activeCount} run(s) ainda em execu\xE7\xE3o ap\xF3s ${timeoutMs}ms \u2014 abandonando a espera`);
|
|
@@ -2951,18 +2906,21 @@ var warned = /* @__PURE__ */ new Set();
|
|
|
2951
2906
|
function warnOutsideRun(member) {
|
|
2952
2907
|
if (warned.has(member)) return;
|
|
2953
2908
|
warned.add(member);
|
|
2954
|
-
const message = `hobots.${member} usado fora de uma execu\xE7\xE3o
|
|
2909
|
+
const message = `hobots.${member} usado fora de uma execu\xE7\xE3o \u2014 ignorado (inicie o agente com register()/start() e use dentro do handler)`;
|
|
2955
2910
|
const logger = activeLogger();
|
|
2956
2911
|
if (logger) logger.warn(message);
|
|
2957
2912
|
else console.warn(`[hobots] ${message}`);
|
|
2958
2913
|
}
|
|
2959
|
-
var
|
|
2914
|
+
var fallbackLogAt = (level) => (message, ...rest) => {
|
|
2915
|
+
const { text, image: dropped } = splitLogArgs(message, rest);
|
|
2916
|
+
printRunLog(level, dropped ? `${text} [imagem descartada]` : text);
|
|
2917
|
+
};
|
|
2960
2918
|
var fallbackLog = {
|
|
2961
|
-
debug: (
|
|
2962
|
-
info: (
|
|
2963
|
-
warn: (
|
|
2964
|
-
error: (
|
|
2965
|
-
success: (
|
|
2919
|
+
debug: fallbackLogAt("debug"),
|
|
2920
|
+
info: fallbackLogAt("info"),
|
|
2921
|
+
warn: fallbackLogAt("warning"),
|
|
2922
|
+
error: fallbackLogAt("error"),
|
|
2923
|
+
success: fallbackLogAt("success")
|
|
2966
2924
|
};
|
|
2967
2925
|
var noop = () => void 0;
|
|
2968
2926
|
var fallbackEachSink = {
|
|
@@ -2972,7 +2930,7 @@ var fallbackEachSink = {
|
|
|
2972
2930
|
advance: noop,
|
|
2973
2931
|
checkCanceled: noop,
|
|
2974
2932
|
isAborted: () => false,
|
|
2975
|
-
logError: (message) => printRunLog(
|
|
2933
|
+
logError: (message) => printRunLog("error", message)
|
|
2976
2934
|
};
|
|
2977
2935
|
var fallbackItems = {
|
|
2978
2936
|
add: noop,
|
|
@@ -2999,7 +2957,6 @@ function method(value) {
|
|
|
2999
2957
|
var hobots = Object.defineProperties({}, {
|
|
3000
2958
|
// ── contexto da run atual (getters) ─────────────────────────────────────────
|
|
3001
2959
|
runId: getter("runId", () => ""),
|
|
3002
|
-
task: getter("task", () => ""),
|
|
3003
2960
|
params: getter("params", () => ({})),
|
|
3004
2961
|
log: getter("log", () => fallbackLog),
|
|
3005
2962
|
items: getter("items", () => fallbackItems),
|
|
@@ -3008,24 +2965,22 @@ var hobots = Object.defineProperties({}, {
|
|
|
3008
2965
|
signal: getter("signal", () => neverAborts),
|
|
3009
2966
|
throwIfCanceled: method(() => resolve("throwIfCanceled")?.throwIfCanceled()),
|
|
3010
2967
|
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
2968
|
retry: method((fn, options) => {
|
|
3015
2969
|
const reporter = currentReporter();
|
|
3016
2970
|
return reporter ? reporter.context.retry(fn, options) : retryWithBackoff(fn, options);
|
|
3017
2971
|
}),
|
|
3018
2972
|
// ── processo ────────────────────────────────────────────────────────────────
|
|
3019
|
-
register: method((
|
|
3020
|
-
register(
|
|
2973
|
+
register: method((handler, options) => {
|
|
2974
|
+
register(handler, options);
|
|
3021
2975
|
installGlobalHobots();
|
|
3022
2976
|
}),
|
|
3023
|
-
start: method((
|
|
3024
|
-
start(
|
|
2977
|
+
start: method((handler, options) => {
|
|
2978
|
+
start(handler, options);
|
|
3025
2979
|
installGlobalHobots();
|
|
3026
2980
|
}),
|
|
3027
2981
|
close: method((timeoutMs) => close(timeoutMs)),
|
|
3028
|
-
captureException: method((error, hint) => captureException(error, hint))
|
|
2982
|
+
captureException: method((error, hint) => captureException(error, hint)),
|
|
2983
|
+
image: method(image)
|
|
3029
2984
|
});
|
|
3030
2985
|
function installGlobalHobots() {
|
|
3031
2986
|
if (globalThis.hobots === hobots) return;
|
|
@@ -3036,6 +2991,7 @@ var start2 = hobots.start;
|
|
|
3036
2991
|
var close2 = hobots.close;
|
|
3037
2992
|
var captureException2 = hobots.captureException;
|
|
3038
2993
|
var retry = hobots.retry;
|
|
2994
|
+
var image2 = hobots.image;
|
|
3039
2995
|
|
|
3040
2996
|
// src/index.ts
|
|
3041
2997
|
var src_default = hobots;
|
|
@@ -3044,6 +3000,7 @@ export {
|
|
|
3044
3000
|
close2 as close,
|
|
3045
3001
|
src_default as default,
|
|
3046
3002
|
hobots,
|
|
3003
|
+
image2 as image,
|
|
3047
3004
|
register2 as register,
|
|
3048
3005
|
retry,
|
|
3049
3006
|
start2 as start
|