hobots 4.2.0 → 4.5.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 +18 -5
- package/dist/index.cjs +331 -103
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -21
- package/dist/index.d.ts +58 -21
- package/dist/index.js +331 -103
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +12 -1
- package/dist/protocol.d.ts +12 -1
- package/docs/api-reference.md +7 -5
- package/docs/concepts.md +3 -3
- package/docs/getting-started.md +4 -2
- package/docs/protocol.md +11 -3
- package/docs/runs.md +11 -11
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -42,62 +42,9 @@ __export(src_exports, {
|
|
|
42
42
|
});
|
|
43
43
|
module.exports = __toCommonJS(src_exports);
|
|
44
44
|
|
|
45
|
-
// src/stacktrace.ts
|
|
46
|
-
var MAX_FRAMES = 50;
|
|
47
|
-
var MAX_FILENAME_CHARS = 1024;
|
|
48
|
-
var FRAME_RE = /^\s*at\s+(?:async\s+)?(?:(.+?)\s+\()?(.+?)\)?$/;
|
|
49
|
-
var LOCATION_RE = /^(.*?):(\d+):(\d+)$/;
|
|
50
|
-
function isInApp(filename) {
|
|
51
|
-
if (!filename) return false;
|
|
52
|
-
if (filename.startsWith("node:")) return false;
|
|
53
|
-
if (filename.startsWith("internal/")) return false;
|
|
54
|
-
if (filename.includes("node_modules")) return false;
|
|
55
|
-
if (filename === "<anonymous>") return false;
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
function parseStacktrace(stack) {
|
|
59
|
-
if (!stack) return [];
|
|
60
|
-
const frames = [];
|
|
61
|
-
for (const line of stack.split("\n")) {
|
|
62
|
-
if (frames.length >= MAX_FRAMES) break;
|
|
63
|
-
const match = FRAME_RE.exec(line);
|
|
64
|
-
if (!match || !line.trimStart().startsWith("at ")) continue;
|
|
65
|
-
const [, fn, location] = match;
|
|
66
|
-
const loc = LOCATION_RE.exec(location ?? "");
|
|
67
|
-
const filename = loc ? loc[1] : location;
|
|
68
|
-
frames.push({
|
|
69
|
-
function: fn || "<anonymous>",
|
|
70
|
-
filename: filename ? filename.slice(0, MAX_FILENAME_CHARS) : void 0,
|
|
71
|
-
lineno: loc?.[2] ? Number(loc[2]) : void 0,
|
|
72
|
-
colno: loc?.[3] ? Number(loc[3]) : void 0,
|
|
73
|
-
in_app: isInApp(filename)
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
return frames;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// src/scrub.ts
|
|
80
|
-
var SECRET_PATTERNS = [
|
|
81
|
-
// client secret da Hobots
|
|
82
|
-
[/hb_[a-f0-9]{16,}/gi, "[REDACTED]"],
|
|
83
|
-
// tokens bearer em headers/mensagens
|
|
84
|
-
[/\b(authorization:?\s*)?bearer\s+[a-z0-9._~+/=-]{8,}/gi, "Bearer [REDACTED]"],
|
|
85
|
-
// chaves no formato sk-… (OpenAI e afins)
|
|
86
|
-
[/\bsk-[a-z0-9_-]{16,}\b/gi, "[REDACTED]"],
|
|
87
|
-
// credenciais embutidas em URL (https://user:pass@host)
|
|
88
|
-
[/\/\/[^/\s:@]+:[^/\s@]+@/g, "//[REDACTED]@"]
|
|
89
|
-
];
|
|
90
|
-
function scrubSecrets(text) {
|
|
91
|
-
let out = text;
|
|
92
|
-
for (const [pattern, replacement] of SECRET_PATTERNS) {
|
|
93
|
-
out = out.replace(pattern, replacement);
|
|
94
|
-
}
|
|
95
|
-
return out;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
45
|
// src/utils.ts
|
|
99
46
|
var SDK_NAME = "hobots";
|
|
100
|
-
var SDK_VERSION = true ? "4.
|
|
47
|
+
var SDK_VERSION = true ? "4.5.0" : "0.0.0-dev";
|
|
101
48
|
function isNode() {
|
|
102
49
|
return typeof process !== "undefined" && typeof process.versions?.node === "string";
|
|
103
50
|
}
|
|
@@ -245,6 +192,104 @@ function colorize(text, color) {
|
|
|
245
192
|
return `${ANSI[color]}${text}${ANSI.reset}`;
|
|
246
193
|
}
|
|
247
194
|
|
|
195
|
+
// src/env.ts
|
|
196
|
+
var INSTANCE_ID_ENV = "HOBOTS_INSTANCE_ID";
|
|
197
|
+
var ENVIRONMENT_ENV = "HOBOTS_ENVIRONMENT";
|
|
198
|
+
var RUN_ID_ENV = "HOBOTS_RUN_ID";
|
|
199
|
+
var dotenvLoaded = false;
|
|
200
|
+
function loadDotenv() {
|
|
201
|
+
if (dotenvLoaded) return;
|
|
202
|
+
dotenvLoaded = true;
|
|
203
|
+
if (!isNode() || typeof process.loadEnvFile !== "function") return;
|
|
204
|
+
try {
|
|
205
|
+
process.loadEnvFile(".env");
|
|
206
|
+
} catch (err) {
|
|
207
|
+
const code = err?.code;
|
|
208
|
+
if (code === "ENOENT") return;
|
|
209
|
+
console.warn(`[hobots] n\xE3o foi poss\xEDvel ler o .env da pasta de trabalho (${typeof code === "string" ? code : "erro desconhecido"}) \u2014 seguindo s\xF3 com as vari\xE1veis do ambiente`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function resolveInstanceId(option) {
|
|
213
|
+
const fromOption = option?.trim();
|
|
214
|
+
if (fromOption) return fromOption;
|
|
215
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
216
|
+
return process.env[INSTANCE_ID_ENV]?.trim() || void 0;
|
|
217
|
+
}
|
|
218
|
+
function resolveRunId(option) {
|
|
219
|
+
const fromOption = option?.trim();
|
|
220
|
+
if (fromOption) return fromOption;
|
|
221
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
222
|
+
return process.env[RUN_ID_ENV]?.trim() || void 0;
|
|
223
|
+
}
|
|
224
|
+
function resolveEnvironmentOption(option) {
|
|
225
|
+
const fromOption = option?.trim();
|
|
226
|
+
if (fromOption) return fromOption;
|
|
227
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
228
|
+
return process.env[ENVIRONMENT_ENV]?.trim() || void 0;
|
|
229
|
+
}
|
|
230
|
+
async function fallbackInstanceId() {
|
|
231
|
+
if (!isNode()) return "browser";
|
|
232
|
+
try {
|
|
233
|
+
const os = await import("os");
|
|
234
|
+
return os.hostname() || "unknown-host";
|
|
235
|
+
} catch {
|
|
236
|
+
return "unknown-host";
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/stacktrace.ts
|
|
241
|
+
var MAX_FRAMES = 50;
|
|
242
|
+
var MAX_FILENAME_CHARS = 1024;
|
|
243
|
+
var FRAME_RE = /^\s*at\s+(?:async\s+)?(?:(.+?)\s+\()?(.+?)\)?$/;
|
|
244
|
+
var LOCATION_RE = /^(.*?):(\d+):(\d+)$/;
|
|
245
|
+
function isInApp(filename) {
|
|
246
|
+
if (!filename) return false;
|
|
247
|
+
if (filename.startsWith("node:")) return false;
|
|
248
|
+
if (filename.startsWith("internal/")) return false;
|
|
249
|
+
if (filename.includes("node_modules")) return false;
|
|
250
|
+
if (filename === "<anonymous>") return false;
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
function parseStacktrace(stack) {
|
|
254
|
+
if (!stack) return [];
|
|
255
|
+
const frames = [];
|
|
256
|
+
for (const line of stack.split("\n")) {
|
|
257
|
+
if (frames.length >= MAX_FRAMES) break;
|
|
258
|
+
const match = FRAME_RE.exec(line);
|
|
259
|
+
if (!match || !line.trimStart().startsWith("at ")) continue;
|
|
260
|
+
const [, fn, location] = match;
|
|
261
|
+
const loc = LOCATION_RE.exec(location ?? "");
|
|
262
|
+
const filename = loc ? loc[1] : location;
|
|
263
|
+
frames.push({
|
|
264
|
+
function: fn || "<anonymous>",
|
|
265
|
+
filename: filename ? filename.slice(0, MAX_FILENAME_CHARS) : void 0,
|
|
266
|
+
lineno: loc?.[2] ? Number(loc[2]) : void 0,
|
|
267
|
+
colno: loc?.[3] ? Number(loc[3]) : void 0,
|
|
268
|
+
in_app: isInApp(filename)
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
return frames;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/scrub.ts
|
|
275
|
+
var SECRET_PATTERNS = [
|
|
276
|
+
// client secret da Hobots
|
|
277
|
+
[/hb_[a-f0-9]{16,}/gi, "[REDACTED]"],
|
|
278
|
+
// tokens bearer em headers/mensagens
|
|
279
|
+
[/\b(authorization:?\s*)?bearer\s+[a-z0-9._~+/=-]{8,}/gi, "Bearer [REDACTED]"],
|
|
280
|
+
// chaves no formato sk-… (OpenAI e afins)
|
|
281
|
+
[/\bsk-[a-z0-9_-]{16,}\b/gi, "[REDACTED]"],
|
|
282
|
+
// credenciais embutidas em URL (https://user:pass@host)
|
|
283
|
+
[/\/\/[^/\s:@]+:[^/\s@]+@/g, "//[REDACTED]@"]
|
|
284
|
+
];
|
|
285
|
+
function scrubSecrets(text) {
|
|
286
|
+
let out = text;
|
|
287
|
+
for (const [pattern, replacement] of SECRET_PATTERNS) {
|
|
288
|
+
out = out.replace(pattern, replacement);
|
|
289
|
+
}
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
|
|
248
293
|
// src/eventbuilder.ts
|
|
249
294
|
var MAX_EXCEPTION_VALUE_CHARS = 8192;
|
|
250
295
|
function eventFromException(error, type = "error") {
|
|
@@ -422,6 +467,10 @@ function clipFingerprint(fingerprint) {
|
|
|
422
467
|
if (fingerprint === void 0) return void 0;
|
|
423
468
|
return fingerprint.slice(0, MAX_FINGERPRINT_ITEMS).map((item) => truncate(String(item), MAX_FINGERPRINT_ITEM_CHARS));
|
|
424
469
|
}
|
|
470
|
+
var captured = /* @__PURE__ */ new WeakSet();
|
|
471
|
+
function wasCaptured(error) {
|
|
472
|
+
return typeof error === "object" && error !== null && captured.has(error);
|
|
473
|
+
}
|
|
425
474
|
function runtimeContexts() {
|
|
426
475
|
if (isNode()) {
|
|
427
476
|
return {
|
|
@@ -473,6 +522,7 @@ var Client = class {
|
|
|
473
522
|
return `Client { baseUrl: '${this.#config.baseUrl}' }`;
|
|
474
523
|
}
|
|
475
524
|
captureException(error, hint) {
|
|
525
|
+
if (typeof error === "object" && error !== null) captured.add(error);
|
|
476
526
|
return this.captureEvent(eventFromException(error, hint?.type ?? "error"), hint);
|
|
477
527
|
}
|
|
478
528
|
captureEvent(event, hint) {
|
|
@@ -580,12 +630,12 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
580
630
|
const origin = fromOption ? "clientSecret" : `env ${CLIENT_SECRET_ENV}`;
|
|
581
631
|
if (!key) {
|
|
582
632
|
throw new Error(
|
|
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
|
|
633
|
+
`${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 na Central: abra o agente \u2192 Configura\xE7\xF5es \u2192 agente \u2192 aba credenciais`
|
|
584
634
|
);
|
|
585
635
|
}
|
|
586
636
|
if (!SECRET_PATTERN.test(key)) {
|
|
587
637
|
throw new Error(
|
|
588
|
-
`${prefix} ${origin} com formato inv\xE1lido (${key.length} caracteres) \u2014 esperado "hb_" + 64 hex; copie o Client Secret
|
|
638
|
+
`${prefix} ${origin} com formato inv\xE1lido (${key.length} caracteres) \u2014 esperado "hb_" + 64 hex; copie o Client Secret na Central: abra o agente \u2192 Configura\xE7\xF5es \u2192 agente \u2192 aba credenciais`
|
|
589
639
|
);
|
|
590
640
|
}
|
|
591
641
|
const baseUrl = validateBaseUrl(envBaseUrl() ?? DEFAULT_BASE_URL, prefix);
|
|
@@ -597,31 +647,6 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
597
647
|
};
|
|
598
648
|
}
|
|
599
649
|
|
|
600
|
-
// src/env.ts
|
|
601
|
-
var INSTANCE_ID_ENV = "HOBOTS_INSTANCE_ID";
|
|
602
|
-
var ENVIRONMENT_ENV = "HOBOTS_ENVIRONMENT";
|
|
603
|
-
function resolveInstanceId(option) {
|
|
604
|
-
const fromOption = option?.trim();
|
|
605
|
-
if (fromOption) return fromOption;
|
|
606
|
-
if (typeof process === "undefined" || !process.env) return void 0;
|
|
607
|
-
return process.env[INSTANCE_ID_ENV]?.trim() || void 0;
|
|
608
|
-
}
|
|
609
|
-
function resolveEnvironmentOption(option) {
|
|
610
|
-
const fromOption = option?.trim();
|
|
611
|
-
if (fromOption) return fromOption;
|
|
612
|
-
if (typeof process === "undefined" || !process.env) return void 0;
|
|
613
|
-
return process.env[ENVIRONMENT_ENV]?.trim() || void 0;
|
|
614
|
-
}
|
|
615
|
-
async function fallbackInstanceId() {
|
|
616
|
-
if (!isNode()) return "browser";
|
|
617
|
-
try {
|
|
618
|
-
const os = await import("os");
|
|
619
|
-
return os.hostname() || "unknown-host";
|
|
620
|
-
} catch {
|
|
621
|
-
return "unknown-host";
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
|
|
625
650
|
// src/heartbeat.ts
|
|
626
651
|
var DEFAULT_INTERVAL_MS = WIRE_LIMITS.HEARTBEAT_DEFAULT_INTERVAL_MS;
|
|
627
652
|
var MIN_REPORTED_INTERVAL_MS = WIRE_LIMITS.HEARTBEAT_MIN_INTERVAL_MS;
|
|
@@ -983,6 +1008,42 @@ var TasksApi = class {
|
|
|
983
1008
|
created_at: typeof body.created_at === "string" ? body.created_at : (/* @__PURE__ */ new Date()).toISOString()
|
|
984
1009
|
};
|
|
985
1010
|
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Anexa o processo a uma run já reivindicada para esta instância (env HOBOTS_RUN_ID):
|
|
1013
|
+
* POST /v1/runs/:id/start. Um request só — quem decide o retry é o AttachedRunner.
|
|
1014
|
+
* 404 (não existe) e 409 (cancelada, já iniciada ou de outra instância) viram
|
|
1015
|
+
* TasksApiError com o status.
|
|
1016
|
+
*/
|
|
1017
|
+
async startRun(runId, request, signal) {
|
|
1018
|
+
const { signal: timeoutSignal, cleanup } = withTimeoutSignal(UPDATE_TIMEOUT_MS, signal);
|
|
1019
|
+
let response;
|
|
1020
|
+
try {
|
|
1021
|
+
response = await fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/start`, {
|
|
1022
|
+
redirect: "error",
|
|
1023
|
+
method: "POST",
|
|
1024
|
+
headers: this.headers(),
|
|
1025
|
+
body: JSON.stringify(request),
|
|
1026
|
+
signal: timeoutSignal
|
|
1027
|
+
});
|
|
1028
|
+
} finally {
|
|
1029
|
+
cleanup();
|
|
1030
|
+
}
|
|
1031
|
+
if (!response.ok) {
|
|
1032
|
+
throw new TasksApiError(`in\xEDcio da run falhou (HTTP ${response.status})`, response.status);
|
|
1033
|
+
}
|
|
1034
|
+
const body = await response.json().catch(() => null);
|
|
1035
|
+
if (typeof body?.id !== "string") {
|
|
1036
|
+
throw new TasksApiError("resposta inv\xE1lida do in\xEDcio da run (id ausente)", 0);
|
|
1037
|
+
}
|
|
1038
|
+
const form = body.form;
|
|
1039
|
+
return {
|
|
1040
|
+
id: body.id,
|
|
1041
|
+
params: body.params && typeof body.params === "object" && !Array.isArray(body.params) ? body.params : {},
|
|
1042
|
+
form: typeof form?.id === "string" && typeof form.name === "string" ? { id: form.id, name: form.name } : null,
|
|
1043
|
+
source: typeof body.source === "string" ? body.source : "hobots",
|
|
1044
|
+
created_at: typeof body.created_at === "string" ? body.created_at : (/* @__PURE__ */ new Date()).toISOString()
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
986
1047
|
/** Nunca confia no shape da resposta: runs malformadas são descartadas e o `max` é respeitado. */
|
|
987
1048
|
parsePollResponse(body, max) {
|
|
988
1049
|
const runs = body?.runs;
|
|
@@ -1240,7 +1301,7 @@ var Backoff = class {
|
|
|
1240
1301
|
this.authFailures++;
|
|
1241
1302
|
if (this.authFailures === 1 || this.authFailures % 10 === 0) {
|
|
1242
1303
|
this.logger.error(
|
|
1243
|
-
error.status === 402 ? "agente fora do limite do plano (402) \u2014 fa\xE7a upgrade em Plano e cobran\xE7a para reativ\xE1-lo" : "chave rejeitada (401) \u2014 verifique o Client Secret
|
|
1304
|
+
error.status === 402 ? "agente fora do limite do plano (402) \u2014 fa\xE7a upgrade em Plano e cobran\xE7a para reativ\xE1-lo" : "chave rejeitada (401) \u2014 verifique o Client Secret na Central: abra o agente \u2192 Configura\xE7\xF5es \u2192 agente \u2192 aba credenciais"
|
|
1244
1305
|
);
|
|
1245
1306
|
}
|
|
1246
1307
|
return Math.max(this.floorMs, AUTH_FAILURE_INTERVAL_MS);
|
|
@@ -1266,6 +1327,9 @@ var TERMINAL_ERROR_NAMES = /* @__PURE__ */ new Set(["RunCanceled", "AbortError",
|
|
|
1266
1327
|
function isTerminalError(error) {
|
|
1267
1328
|
return error instanceof Error && TERMINAL_ERROR_NAMES.has(error.name);
|
|
1268
1329
|
}
|
|
1330
|
+
function isCancellation(error) {
|
|
1331
|
+
return error instanceof Error && (error.name === "RunCanceled" || error.name === "AbortError");
|
|
1332
|
+
}
|
|
1269
1333
|
|
|
1270
1334
|
// src/tasks/each.ts
|
|
1271
1335
|
function thrownMessage(error) {
|
|
@@ -1331,16 +1395,15 @@ async function eachItem(list, handler, options, sink) {
|
|
|
1331
1395
|
if (!handle.reported) handle.succeeded();
|
|
1332
1396
|
} catch (error) {
|
|
1333
1397
|
if (isTerminalError(error) || sink.isAborted()) {
|
|
1334
|
-
step?.setStatus("cancelled");
|
|
1335
|
-
step?.finish();
|
|
1398
|
+
step?.setStatus("cancelled").succeeded();
|
|
1336
1399
|
throw error;
|
|
1337
1400
|
}
|
|
1338
1401
|
const message = thrownMessage(error);
|
|
1339
1402
|
if (!handle.reported) handle.failed(message);
|
|
1340
|
-
step?.
|
|
1403
|
+
step?.failed(error);
|
|
1341
1404
|
sink.logError(`item ${description} falhou: ${message}`);
|
|
1342
1405
|
}
|
|
1343
|
-
step?.
|
|
1406
|
+
step?.succeeded();
|
|
1344
1407
|
if (options.progress !== false) sink.advance();
|
|
1345
1408
|
result.total += 1;
|
|
1346
1409
|
result[handle.status ?? "succeeded"] += 1;
|
|
@@ -1782,7 +1845,7 @@ var RunReporter = class {
|
|
|
1782
1845
|
tx.setStepGuard(checkCanceled);
|
|
1783
1846
|
const eachSink = {
|
|
1784
1847
|
report: (status, message, payload, id, image3) => this.addItem(status, message, payload, id, image3),
|
|
1785
|
-
startStep: (op, description) => tx.
|
|
1848
|
+
startStep: (op, description) => tx.start(op, description),
|
|
1786
1849
|
declareTotal: (total) => {
|
|
1787
1850
|
if (this.progressTotal == null) this.setProgress(this.progressCurrent, total);
|
|
1788
1851
|
},
|
|
@@ -1875,8 +1938,17 @@ var RunReporter = class {
|
|
|
1875
1938
|
itemSeq = 0;
|
|
1876
1939
|
/** Servidor sem o recurso (404) — para de tentar (e de avisar) nesta run. */
|
|
1877
1940
|
itemsUnsupported = false;
|
|
1878
|
-
/**
|
|
1941
|
+
/**
|
|
1942
|
+
* Cota de itens por run do servidor (429, legado — hoje a api não tem teto de itens) —
|
|
1943
|
+
* degrada com um aviso só.
|
|
1944
|
+
*/
|
|
1879
1945
|
itemsQuotaExceeded = false;
|
|
1946
|
+
/**
|
|
1947
|
+
* Armazenamento do plano cheio (402 `storage_limit`): o lote é descartado e o aviso sai
|
|
1948
|
+
* uma vez por episódio. Os envios continuam — num agente `start()` o espaço pode ser
|
|
1949
|
+
* liberado (ou o plano trocado) sem reiniciar; um lote aceito rearma o aviso.
|
|
1950
|
+
*/
|
|
1951
|
+
itemsStorageFull = false;
|
|
1880
1952
|
/**
|
|
1881
1953
|
* Só o kill() do exit path fecha os itens — `dead` (run cancelada/finalizada no
|
|
1882
1954
|
* servidor) NÃO fecha: o servidor aceita itens de run terminal (trabalho já feito).
|
|
@@ -2186,6 +2258,7 @@ var RunReporter = class {
|
|
|
2186
2258
|
if (this.itemsUnsupported) return;
|
|
2187
2259
|
try {
|
|
2188
2260
|
await this.api.createItems(this.runId, { items: batch });
|
|
2261
|
+
this.itemsStorageFull = false;
|
|
2189
2262
|
} catch (error) {
|
|
2190
2263
|
if (error instanceof TasksApiError && error.status === 404) {
|
|
2191
2264
|
this.itemsUnsupported = true;
|
|
@@ -2199,6 +2272,13 @@ var RunReporter = class {
|
|
|
2199
2272
|
}
|
|
2200
2273
|
return;
|
|
2201
2274
|
}
|
|
2275
|
+
if (error instanceof TasksApiError && error.status === 402) {
|
|
2276
|
+
if (!this.itemsStorageFull) {
|
|
2277
|
+
this.itemsStorageFull = true;
|
|
2278
|
+
this.log("warning", "itens descartados: armazenamento do plano cheio \u2014 libere espa\xE7o ou fa\xE7a upgrade em Plano e cobran\xE7a");
|
|
2279
|
+
}
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
2202
2282
|
if (error instanceof TasksApiError) {
|
|
2203
2283
|
this.logger.error(`lote de ${batch.length} item(ns) rejeitado pelo servidor:`, error);
|
|
2204
2284
|
this.log("warning", `${batch.length} item(ns) descartado(s): envio rejeitado pelo servidor (HTTP ${error.status})`);
|
|
@@ -2483,16 +2563,19 @@ var Step = class {
|
|
|
2483
2563
|
return this;
|
|
2484
2564
|
}
|
|
2485
2565
|
/**
|
|
2486
|
-
* Marca a step como `error` guardando o motivo em `data.error` ({ type, message })
|
|
2487
|
-
* Aceita qualquer valor lançado; `message` sobrescreve a mensagem do erro.
|
|
2566
|
+
* Marca a step como `error` guardando o motivo em `data.error` ({ type, message }) e a
|
|
2567
|
+
* fecha. Aceita qualquer valor lançado; `message` sobrescreve a mensagem do erro.
|
|
2488
2568
|
* `options.image` pendura um print do estado da tela em `data.attachments`.
|
|
2569
|
+
* Numa step já fechada não faz nada.
|
|
2489
2570
|
*/
|
|
2490
|
-
|
|
2571
|
+
failed(error, message, options) {
|
|
2572
|
+
if (this.endTimestamp) return this;
|
|
2491
2573
|
this.status = "error";
|
|
2492
2574
|
const type = error instanceof Error ? error.name : typeof error === "object" && error !== null ? "Error" : void 0;
|
|
2493
2575
|
const raw = message ?? (error instanceof Error ? error.message || error.name : String(error));
|
|
2494
2576
|
this.setData("error", { ...type ? { type } : {}, message: stepMessage(raw) });
|
|
2495
2577
|
if (options?.image !== void 0) this.attachImage(options.image);
|
|
2578
|
+
this.close();
|
|
2496
2579
|
return this;
|
|
2497
2580
|
}
|
|
2498
2581
|
/**
|
|
@@ -2507,7 +2590,7 @@ var Step = class {
|
|
|
2507
2590
|
if (!id) return;
|
|
2508
2591
|
this.setData("attachments", [...current, id]);
|
|
2509
2592
|
}
|
|
2510
|
-
/** Motivo registrado via setStatus(…, message) ou
|
|
2593
|
+
/** Motivo registrado via setStatus(…, message) ou failed — usado no log de FINISH. */
|
|
2511
2594
|
reason() {
|
|
2512
2595
|
const error = this.data?.error;
|
|
2513
2596
|
const text = typeof error?.message === "string" ? error.message : this.data?.message;
|
|
@@ -2518,10 +2601,18 @@ var Step = class {
|
|
|
2518
2601
|
return this;
|
|
2519
2602
|
}
|
|
2520
2603
|
/** Step aninhada (filha desta step). */
|
|
2521
|
-
|
|
2604
|
+
start(op, description) {
|
|
2522
2605
|
return this.transaction.createStep(this.stepId, op, description);
|
|
2523
2606
|
}
|
|
2524
|
-
|
|
2607
|
+
/**
|
|
2608
|
+
* Fecha a step. O status fica o que estiver (`ok` por padrão, ou o de `setStatus`).
|
|
2609
|
+
* Idempotente; a transaction fecha no fim da run o que ficou aberto.
|
|
2610
|
+
*/
|
|
2611
|
+
succeeded() {
|
|
2612
|
+
this.close();
|
|
2613
|
+
return this;
|
|
2614
|
+
}
|
|
2615
|
+
close() {
|
|
2525
2616
|
if (this.endTimestamp) return;
|
|
2526
2617
|
this.endTimestamp = nowISO();
|
|
2527
2618
|
this.transaction.emit(
|
|
@@ -2614,7 +2705,7 @@ var Transaction = class {
|
|
|
2614
2705
|
}
|
|
2615
2706
|
/**
|
|
2616
2707
|
* Conecta a checagem de cancelamento chamada antes de cada step nova — é assim que
|
|
2617
|
-
* `hobots.transaction.
|
|
2708
|
+
* `hobots.transaction.start()` (e `step.start()`) param uma run cancelada.
|
|
2618
2709
|
*/
|
|
2619
2710
|
setStepGuard(guard) {
|
|
2620
2711
|
this.stepGuard = guard;
|
|
@@ -2626,10 +2717,32 @@ var Transaction = class {
|
|
|
2626
2717
|
this.emit("step", "start", stepLabel(op, description));
|
|
2627
2718
|
return step;
|
|
2628
2719
|
}
|
|
2629
|
-
/** Step direta da transaction. */
|
|
2630
|
-
|
|
2720
|
+
/** Step direta da transaction. `op` é o nome usado por `succeeded(name)`/`failed(error, name)`. */
|
|
2721
|
+
start(op, description) {
|
|
2631
2722
|
return this.createStep(this.stepId, op, description);
|
|
2632
2723
|
}
|
|
2724
|
+
/**
|
|
2725
|
+
* Fecha a step aberta mais recente — com `name`, a mais recente aberta com esse `op`.
|
|
2726
|
+
* Sem step aberta (ou sem nenhuma com esse nome), não faz nada e devolve undefined.
|
|
2727
|
+
*/
|
|
2728
|
+
succeeded(name) {
|
|
2729
|
+
return this.findOpenStep(name)?.succeeded();
|
|
2730
|
+
}
|
|
2731
|
+
/**
|
|
2732
|
+
* Marca como `error` e fecha a step aberta mais recente — com `name`, a mais recente
|
|
2733
|
+
* aberta com esse `op`. `options.message` sobrescreve a mensagem do erro e
|
|
2734
|
+
* `options.image` pendura um print. Sem step aberta, não faz nada e devolve undefined.
|
|
2735
|
+
*/
|
|
2736
|
+
failed(error, name, options) {
|
|
2737
|
+
return this.findOpenStep(name)?.failed(error, options?.message, options);
|
|
2738
|
+
}
|
|
2739
|
+
findOpenStep(name) {
|
|
2740
|
+
for (let i = this.steps.length - 1; i >= 0; i--) {
|
|
2741
|
+
const step = this.steps[i];
|
|
2742
|
+
if (!step.endTimestamp && (name === void 0 || step.op === name)) return step;
|
|
2743
|
+
}
|
|
2744
|
+
return void 0;
|
|
2745
|
+
}
|
|
2633
2746
|
setTag(key, value) {
|
|
2634
2747
|
this.tags[key] = value;
|
|
2635
2748
|
return this;
|
|
@@ -2667,7 +2780,7 @@ var Transaction = class {
|
|
|
2667
2780
|
remaining.push(step);
|
|
2668
2781
|
}
|
|
2669
2782
|
}
|
|
2670
|
-
for (const step of batch) step.
|
|
2783
|
+
for (const step of batch) step.succeeded();
|
|
2671
2784
|
this.steps.splice(0, this.steps.length, ...remaining);
|
|
2672
2785
|
if (this.client) {
|
|
2673
2786
|
for (let i = 0; i < batch.length; i += MAX_STEPS) {
|
|
@@ -2699,7 +2812,7 @@ var Transaction = class {
|
|
|
2699
2812
|
finish() {
|
|
2700
2813
|
if (this.finished) return;
|
|
2701
2814
|
this.finished = true;
|
|
2702
|
-
for (const step of this.steps) step.
|
|
2815
|
+
for (const step of this.steps) step.succeeded();
|
|
2703
2816
|
const endTimestamp = nowISO();
|
|
2704
2817
|
this.emit(
|
|
2705
2818
|
"transaction",
|
|
@@ -2761,6 +2874,7 @@ async function executeRun(input) {
|
|
|
2761
2874
|
await reporter.finalizeItems();
|
|
2762
2875
|
tx.setStatus("error");
|
|
2763
2876
|
tx.finish();
|
|
2877
|
+
captureRunFailure(client, error, reporter);
|
|
2764
2878
|
reporter.reportFailure(toRunError(error));
|
|
2765
2879
|
logger.debug(`run ${run.id} falhou: ${String(error)}`);
|
|
2766
2880
|
}
|
|
@@ -2770,6 +2884,11 @@ async function executeRun(input) {
|
|
|
2770
2884
|
tracker.remove(reporter);
|
|
2771
2885
|
}
|
|
2772
2886
|
}
|
|
2887
|
+
function captureRunFailure(client, error, reporter) {
|
|
2888
|
+
if (!client?.options.captureRunErrors) return;
|
|
2889
|
+
if (reporter.isDead || isCancellation(error) || wasCaptured(error)) return;
|
|
2890
|
+
client.captureException(error, { type: "error", tags: { mechanism: "run_handler", run_id: reporter.runId } });
|
|
2891
|
+
}
|
|
2773
2892
|
function warnIncompleteProgress(reporter) {
|
|
2774
2893
|
const snapshot = reporter.context.progress.snapshot;
|
|
2775
2894
|
if (!snapshot || snapshot.current >= snapshot.total) return;
|
|
@@ -2961,6 +3080,86 @@ var TaskAgent = class {
|
|
|
2961
3080
|
}
|
|
2962
3081
|
};
|
|
2963
3082
|
|
|
3083
|
+
// src/tasks/attached.ts
|
|
3084
|
+
var START_RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
|
|
3085
|
+
function isTransient(error) {
|
|
3086
|
+
if (!(error instanceof TasksApiError)) return true;
|
|
3087
|
+
return error.status >= 500 || error.status === 429;
|
|
3088
|
+
}
|
|
3089
|
+
function describeRejection(runId, error) {
|
|
3090
|
+
if (error instanceof TasksApiError && error.status === 404) {
|
|
3091
|
+
return `run ${runId} n\xE3o existe na plataforma (404) \u2014 nada a executar`;
|
|
3092
|
+
}
|
|
3093
|
+
if (error instanceof TasksApiError && error.status === 409) {
|
|
3094
|
+
return `run ${runId} n\xE3o pode ser iniciada (409: cancelada, j\xE1 iniciada ou de outra inst\xE2ncia) \u2014 nada a executar`;
|
|
3095
|
+
}
|
|
3096
|
+
return `n\xE3o foi poss\xEDvel iniciar a run ${runId} (${String(error)}) \u2014 nada a executar`;
|
|
3097
|
+
}
|
|
3098
|
+
var AttachedRunner = class {
|
|
3099
|
+
constructor(runId, handler, deps) {
|
|
3100
|
+
this.runId = runId;
|
|
3101
|
+
this.handler = handler;
|
|
3102
|
+
this.deps = deps;
|
|
3103
|
+
this.done = new Promise((resolve2) => {
|
|
3104
|
+
this.resolveDone = resolve2;
|
|
3105
|
+
});
|
|
3106
|
+
}
|
|
3107
|
+
runId;
|
|
3108
|
+
handler;
|
|
3109
|
+
deps;
|
|
3110
|
+
abort = new AbortController();
|
|
3111
|
+
/** Resolve quando a run terminou (ou não pôde ser iniciada, ou o runner foi parado antes). */
|
|
3112
|
+
done;
|
|
3113
|
+
resolveDone;
|
|
3114
|
+
async run() {
|
|
3115
|
+
try {
|
|
3116
|
+
const run = await this.startRun();
|
|
3117
|
+
if (!run) return;
|
|
3118
|
+
this.deps.logger.debug(`run ${run.id} iniciada (HOBOTS_RUN_ID)`);
|
|
3119
|
+
await executeRun({
|
|
3120
|
+
run,
|
|
3121
|
+
handler: this.handler,
|
|
3122
|
+
api: this.deps.api,
|
|
3123
|
+
client: this.deps.client,
|
|
3124
|
+
logger: this.deps.logger,
|
|
3125
|
+
tracker: this.deps.tracker
|
|
3126
|
+
});
|
|
3127
|
+
} catch (error) {
|
|
3128
|
+
this.deps.logger.error("erro inesperado na execu\xE7\xE3o do agente:", error);
|
|
3129
|
+
} finally {
|
|
3130
|
+
this.resolveDone();
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
/** Desiste de iniciar a run (close() antes da plataforma responder). Uma run já iniciada segue pelo tracker. */
|
|
3134
|
+
stop() {
|
|
3135
|
+
this.abort.abort();
|
|
3136
|
+
}
|
|
3137
|
+
async startRun() {
|
|
3138
|
+
for (let attempt = 0; ; attempt++) {
|
|
3139
|
+
if (this.abort.signal.aborted) return void 0;
|
|
3140
|
+
try {
|
|
3141
|
+
return await this.deps.api.startRun(this.runId, { instance: this.deps.instanceInfo() }, this.abort.signal);
|
|
3142
|
+
} catch (error) {
|
|
3143
|
+
if (this.abort.signal.aborted) return void 0;
|
|
3144
|
+
if (!isTransient(error)) {
|
|
3145
|
+
this.deps.logger.error(describeRejection(this.runId, error));
|
|
3146
|
+
return void 0;
|
|
3147
|
+
}
|
|
3148
|
+
const delay = START_RETRY_DELAYS_MS[attempt];
|
|
3149
|
+
if (delay === void 0) {
|
|
3150
|
+
this.deps.logger.error(
|
|
3151
|
+
`n\xE3o foi poss\xEDvel iniciar a run ${this.runId} ap\xF3s ${attempt + 1} tentativas (${String(error)}) \u2014 desistindo`
|
|
3152
|
+
);
|
|
3153
|
+
return void 0;
|
|
3154
|
+
}
|
|
3155
|
+
const wait = jitter(delay);
|
|
3156
|
+
this.deps.logger.warn(`in\xEDcio da run ${this.runId} falhou (${String(error)}), pr\xF3xima tentativa em ${wait}ms`);
|
|
3157
|
+
await abortableSleep(wait, this.abort.signal);
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
};
|
|
3162
|
+
|
|
2964
3163
|
// src/tasks/item-columns.ts
|
|
2965
3164
|
var KEY_RE = /^[a-zA-Z0-9_.-]{1,100}$/;
|
|
2966
3165
|
var MAX_COLUMNS = WIRE_LIMITS.MAX_ITEM_COLUMNS;
|
|
@@ -3166,11 +3365,18 @@ function hostname() {
|
|
|
3166
3365
|
return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
|
|
3167
3366
|
}
|
|
3168
3367
|
function createRuntime(options, handler) {
|
|
3368
|
+
loadDotenv();
|
|
3169
3369
|
const config = resolveConfig({ clientSecret: options.clientSecret });
|
|
3170
3370
|
const logger = makeLogger(options.debug ?? false);
|
|
3171
3371
|
const environment = resolveEnvironmentOption(options.environment);
|
|
3172
3372
|
const client = new Client(
|
|
3173
|
-
{
|
|
3373
|
+
{
|
|
3374
|
+
environment,
|
|
3375
|
+
release: options.release,
|
|
3376
|
+
tags: options.tags,
|
|
3377
|
+
debug: options.debug,
|
|
3378
|
+
captureRunErrors: options.captureUnhandled !== false
|
|
3379
|
+
},
|
|
3174
3380
|
config
|
|
3175
3381
|
);
|
|
3176
3382
|
const api = new TasksApi({ baseUrl: config.runsBaseUrl, key: config.key, logger });
|
|
@@ -3186,6 +3392,8 @@ function createRuntime(options, handler) {
|
|
|
3186
3392
|
heartbeat: void 0,
|
|
3187
3393
|
agent: void 0,
|
|
3188
3394
|
local: void 0,
|
|
3395
|
+
attached: void 0,
|
|
3396
|
+
runId: void 0,
|
|
3189
3397
|
instanceId: resolveInstanceId(options.instanceId),
|
|
3190
3398
|
environment,
|
|
3191
3399
|
pollIntervalMs: void 0,
|
|
@@ -3263,6 +3471,20 @@ function launchLocal(rt) {
|
|
|
3263
3471
|
rt.local = runner;
|
|
3264
3472
|
void runner.run();
|
|
3265
3473
|
}
|
|
3474
|
+
function launchAttached(rt, runId) {
|
|
3475
|
+
const runner = new AttachedRunner(runId, rt.handler, {
|
|
3476
|
+
api: rt.api,
|
|
3477
|
+
client: rt.client,
|
|
3478
|
+
logger: rt.logger,
|
|
3479
|
+
tracker: rt.tracker,
|
|
3480
|
+
instanceInfo: () => instanceInfo(rt)
|
|
3481
|
+
});
|
|
3482
|
+
rt.attached = runner;
|
|
3483
|
+
void runner.run();
|
|
3484
|
+
void runner.done.then(() => {
|
|
3485
|
+
if (runtime === rt) void close();
|
|
3486
|
+
});
|
|
3487
|
+
}
|
|
3266
3488
|
async function boot(rt) {
|
|
3267
3489
|
if (!rt.instanceId) {
|
|
3268
3490
|
const id = await fallbackInstanceId();
|
|
@@ -3282,8 +3504,9 @@ async function boot(rt) {
|
|
|
3282
3504
|
if (rt.options.installExitHandlers !== false) {
|
|
3283
3505
|
rt.uninstallShutdown = installShutdownHandlers(rt.tracker);
|
|
3284
3506
|
}
|
|
3285
|
-
if (rt.handler.mode === "
|
|
3286
|
-
else
|
|
3507
|
+
if (rt.handler.mode === "local") launchLocal(rt);
|
|
3508
|
+
else if (rt.runId) launchAttached(rt, rt.runId);
|
|
3509
|
+
else ensureAgent(rt).start();
|
|
3287
3510
|
}
|
|
3288
3511
|
function scheduleBoot(rt) {
|
|
3289
3512
|
if (rt.bootScheduled) return;
|
|
@@ -3301,6 +3524,7 @@ function startAgent(fn, mode, handler, options) {
|
|
|
3301
3524
|
const agentHandler = { mode, handler, options: resolveRunOptions(options, mode, logger) };
|
|
3302
3525
|
const rt = createRuntime(options, agentHandler);
|
|
3303
3526
|
if (mode === "remote" && options.pollIntervalMs !== void 0) rt.pollIntervalMs = options.pollIntervalMs;
|
|
3527
|
+
if (mode === "remote") rt.runId = resolveRunId(options.runId);
|
|
3304
3528
|
runtime = rt;
|
|
3305
3529
|
scheduleBoot(rt);
|
|
3306
3530
|
}
|
|
@@ -3341,7 +3565,9 @@ function captureException(error, hint) {
|
|
|
3341
3565
|
const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
3342
3566
|
forwardAttachment(rt, hint.attachment, message, hint.type ?? "error");
|
|
3343
3567
|
}
|
|
3344
|
-
|
|
3568
|
+
const runId = currentReporter()?.runId;
|
|
3569
|
+
const withRun = runId ? { ...hint, tags: { run_id: runId, ...hint?.tags } } : hint;
|
|
3570
|
+
return rt.client.captureException(error, withRun);
|
|
3345
3571
|
}
|
|
3346
3572
|
async function close(timeoutMs = 3e4) {
|
|
3347
3573
|
const rt = runtime;
|
|
@@ -3350,6 +3576,7 @@ async function close(timeoutMs = 3e4) {
|
|
|
3350
3576
|
await rt.bootPromise;
|
|
3351
3577
|
if (rt.agent) await rt.agent.stop(0);
|
|
3352
3578
|
rt.local?.stop();
|
|
3579
|
+
rt.attached?.stop();
|
|
3353
3580
|
const idle = await rt.tracker.waitIdle(timeoutMs);
|
|
3354
3581
|
if (!idle) {
|
|
3355
3582
|
rt.logger.warn(`${rt.tracker.activeCount} run(s) ainda em execu\xE7\xE3o ap\xF3s ${timeoutMs}ms \u2014 abandonando a espera`);
|
|
@@ -3467,5 +3694,6 @@ var image2 = hobots.image;
|
|
|
3467
3694
|
var isFileRef2 = hobots.isFileRef;
|
|
3468
3695
|
|
|
3469
3696
|
// src/index.ts
|
|
3697
|
+
loadDotenv();
|
|
3470
3698
|
var src_default = hobots;
|
|
3471
3699
|
//# sourceMappingURL=index.cjs.map
|