hobots 4.2.0 → 5.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 +27 -11
- package/dist/cli.cjs +250 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/index.cjs +796 -246
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +91 -229
- package/dist/index.d.ts +91 -229
- package/dist/index.js +796 -246
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +17 -3
- package/dist/protocol.d.ts +17 -3
- package/docs/README.md +2 -2
- package/docs/api-reference.md +25 -9
- package/docs/concepts.md +12 -12
- package/docs/getting-started.md +5 -3
- package/docs/protocol.md +16 -8
- package/docs/runs.md +97 -23
- package/package.json +4 -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 ? "
|
|
47
|
+
var SDK_VERSION = true ? "5.0.0" : "0.0.0-dev";
|
|
101
48
|
function isNode() {
|
|
102
49
|
return typeof process !== "undefined" && typeof process.versions?.node === "string";
|
|
103
50
|
}
|
|
@@ -150,8 +97,13 @@ function abortableSleep(ms, signal) {
|
|
|
150
97
|
function jitter(baseMs) {
|
|
151
98
|
return Math.floor(baseMs / 2 + Math.random() * (baseMs / 2));
|
|
152
99
|
}
|
|
100
|
+
var TRUNCATION_SUFFIX = "\u2026 [truncado]";
|
|
153
101
|
function truncate(value, max) {
|
|
154
|
-
return value.length > max ? `${value.slice(0, max)}
|
|
102
|
+
return value.length > max ? `${value.slice(0, max)}${TRUNCATION_SUFFIX}` : value;
|
|
103
|
+
}
|
|
104
|
+
function clip(value, max) {
|
|
105
|
+
if (value.length <= max) return value;
|
|
106
|
+
return `${value.slice(0, Math.max(0, max - TRUNCATION_SUFFIX.length))}${TRUNCATION_SUFFIX}`;
|
|
155
107
|
}
|
|
156
108
|
function safeString(value) {
|
|
157
109
|
if (typeof value === "string") return value;
|
|
@@ -245,6 +197,124 @@ function colorize(text, color) {
|
|
|
245
197
|
return `${ANSI[color]}${text}${ANSI.reset}`;
|
|
246
198
|
}
|
|
247
199
|
|
|
200
|
+
// src/env.ts
|
|
201
|
+
var INSTANCE_ID_ENV = "HOBOTS_INSTANCE_ID";
|
|
202
|
+
var ENVIRONMENT_ENV = "HOBOTS_ENVIRONMENT";
|
|
203
|
+
var RUN_ID_ENV = "HOBOTS_RUN_ID";
|
|
204
|
+
var LOCAL_RUN_ENV = "HOBOTS_LOCAL_RUN";
|
|
205
|
+
var RUN_PARAMS_ENV = "HOBOTS_RUN_PARAMS";
|
|
206
|
+
var dotenvLoaded = false;
|
|
207
|
+
function loadDotenv() {
|
|
208
|
+
if (dotenvLoaded) return;
|
|
209
|
+
dotenvLoaded = true;
|
|
210
|
+
if (!isNode() || typeof process.loadEnvFile !== "function") return;
|
|
211
|
+
try {
|
|
212
|
+
process.loadEnvFile(".env");
|
|
213
|
+
} catch (err) {
|
|
214
|
+
const code = err?.code;
|
|
215
|
+
if (code === "ENOENT") return;
|
|
216
|
+
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`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function resolveInstanceId(option) {
|
|
220
|
+
const fromOption = option?.trim();
|
|
221
|
+
if (fromOption) return fromOption;
|
|
222
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
223
|
+
return process.env[INSTANCE_ID_ENV]?.trim() || void 0;
|
|
224
|
+
}
|
|
225
|
+
function resolveRunId(option) {
|
|
226
|
+
const fromOption = option?.trim();
|
|
227
|
+
if (fromOption) return fromOption;
|
|
228
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
229
|
+
return process.env[RUN_ID_ENV]?.trim() || void 0;
|
|
230
|
+
}
|
|
231
|
+
function isLocalRun() {
|
|
232
|
+
if (typeof process === "undefined" || !process.env) return false;
|
|
233
|
+
return process.env[LOCAL_RUN_ENV]?.trim() === "1";
|
|
234
|
+
}
|
|
235
|
+
function resolveLocalRunParams() {
|
|
236
|
+
const raw = process.env[RUN_PARAMS_ENV]?.trim();
|
|
237
|
+
if (!raw) return {};
|
|
238
|
+
let parsed;
|
|
239
|
+
try {
|
|
240
|
+
parsed = JSON.parse(raw);
|
|
241
|
+
} catch {
|
|
242
|
+
throw new Error(`${RUN_PARAMS_ENV} inv\xE1lido: n\xE3o \xE9 JSON v\xE1lido`);
|
|
243
|
+
}
|
|
244
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
245
|
+
throw new Error(`${RUN_PARAMS_ENV} inv\xE1lido: esperado um objeto JSON`);
|
|
246
|
+
}
|
|
247
|
+
return parsed;
|
|
248
|
+
}
|
|
249
|
+
function resolveEnvironmentOption(option) {
|
|
250
|
+
const fromOption = option?.trim();
|
|
251
|
+
if (fromOption) return fromOption;
|
|
252
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
253
|
+
return process.env[ENVIRONMENT_ENV]?.trim() || void 0;
|
|
254
|
+
}
|
|
255
|
+
async function fallbackInstanceId() {
|
|
256
|
+
if (!isNode()) return "browser";
|
|
257
|
+
try {
|
|
258
|
+
const os = await import("os");
|
|
259
|
+
return os.hostname() || "unknown-host";
|
|
260
|
+
} catch {
|
|
261
|
+
return "unknown-host";
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/stacktrace.ts
|
|
266
|
+
var MAX_FRAMES = 50;
|
|
267
|
+
var MAX_FILENAME_CHARS = 1024;
|
|
268
|
+
var FRAME_RE = /^\s*at\s+(?:async\s+)?(?:(.+?)\s+\()?(.+?)\)?$/;
|
|
269
|
+
var LOCATION_RE = /^(.*?):(\d+):(\d+)$/;
|
|
270
|
+
function isInApp(filename) {
|
|
271
|
+
if (!filename) return false;
|
|
272
|
+
if (filename.startsWith("node:")) return false;
|
|
273
|
+
if (filename.startsWith("internal/")) return false;
|
|
274
|
+
if (filename.includes("node_modules")) return false;
|
|
275
|
+
if (filename === "<anonymous>") return false;
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
function parseStacktrace(stack) {
|
|
279
|
+
if (!stack) return [];
|
|
280
|
+
const frames = [];
|
|
281
|
+
for (const line of stack.split("\n")) {
|
|
282
|
+
if (frames.length >= MAX_FRAMES) break;
|
|
283
|
+
const match = FRAME_RE.exec(line);
|
|
284
|
+
if (!match || !line.trimStart().startsWith("at ")) continue;
|
|
285
|
+
const [, fn, location] = match;
|
|
286
|
+
const loc = LOCATION_RE.exec(location ?? "");
|
|
287
|
+
const filename = loc ? loc[1] : location;
|
|
288
|
+
frames.push({
|
|
289
|
+
function: fn || "<anonymous>",
|
|
290
|
+
filename: filename ? filename.slice(0, MAX_FILENAME_CHARS) : void 0,
|
|
291
|
+
lineno: loc?.[2] ? Number(loc[2]) : void 0,
|
|
292
|
+
colno: loc?.[3] ? Number(loc[3]) : void 0,
|
|
293
|
+
in_app: isInApp(filename)
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
return frames;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/scrub.ts
|
|
300
|
+
var SECRET_PATTERNS = [
|
|
301
|
+
// client secret da Hobots
|
|
302
|
+
[/hb_[a-f0-9]{16,}/gi, "[REDACTED]"],
|
|
303
|
+
// tokens bearer em headers/mensagens
|
|
304
|
+
[/\b(authorization:?\s*)?bearer\s+[a-z0-9._~+/=-]{8,}/gi, "Bearer [REDACTED]"],
|
|
305
|
+
// chaves no formato sk-… (OpenAI e afins)
|
|
306
|
+
[/\bsk-[a-z0-9_-]{16,}\b/gi, "[REDACTED]"],
|
|
307
|
+
// credenciais embutidas em URL (https://user:pass@host)
|
|
308
|
+
[/\/\/[^/\s:@]+:[^/\s@]+@/g, "//[REDACTED]@"]
|
|
309
|
+
];
|
|
310
|
+
function scrubSecrets(text) {
|
|
311
|
+
let out = text;
|
|
312
|
+
for (const [pattern, replacement] of SECRET_PATTERNS) {
|
|
313
|
+
out = out.replace(pattern, replacement);
|
|
314
|
+
}
|
|
315
|
+
return out;
|
|
316
|
+
}
|
|
317
|
+
|
|
248
318
|
// src/eventbuilder.ts
|
|
249
319
|
var MAX_EXCEPTION_VALUE_CHARS = 8192;
|
|
250
320
|
function eventFromException(error, type = "error") {
|
|
@@ -415,13 +485,26 @@ var MAX_FINGERPRINT_ITEMS = 10;
|
|
|
415
485
|
var MAX_FINGERPRINT_ITEM_CHARS = 255;
|
|
416
486
|
var MAX_TRANSACTION_NAME_CHARS = 500;
|
|
417
487
|
var MAX_STEPS = 200;
|
|
488
|
+
var MAX_STEP_DESCRIPTION_CHARS = 2048;
|
|
489
|
+
var MAX_STEP_OP_CHARS = 255;
|
|
418
490
|
function clipTags(tags) {
|
|
419
491
|
return Object.fromEntries(Object.entries(tags).map(([key, value]) => [key, truncate(String(value), MAX_TAG_VALUE_CHARS)]));
|
|
420
492
|
}
|
|
493
|
+
function clipStep(step) {
|
|
494
|
+
return {
|
|
495
|
+
...step,
|
|
496
|
+
...step.op ? { op: clip(step.op, MAX_STEP_OP_CHARS) } : {},
|
|
497
|
+
...step.description ? { description: clip(step.description, MAX_STEP_DESCRIPTION_CHARS) } : {}
|
|
498
|
+
};
|
|
499
|
+
}
|
|
421
500
|
function clipFingerprint(fingerprint) {
|
|
422
501
|
if (fingerprint === void 0) return void 0;
|
|
423
502
|
return fingerprint.slice(0, MAX_FINGERPRINT_ITEMS).map((item) => truncate(String(item), MAX_FINGERPRINT_ITEM_CHARS));
|
|
424
503
|
}
|
|
504
|
+
var captured = /* @__PURE__ */ new WeakSet();
|
|
505
|
+
function wasCaptured(error) {
|
|
506
|
+
return typeof error === "object" && error !== null && captured.has(error);
|
|
507
|
+
}
|
|
425
508
|
function runtimeContexts() {
|
|
426
509
|
if (isNode()) {
|
|
427
510
|
return {
|
|
@@ -472,11 +555,13 @@ var Client = class {
|
|
|
472
555
|
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
|
473
556
|
return `Client { baseUrl: '${this.#config.baseUrl}' }`;
|
|
474
557
|
}
|
|
475
|
-
|
|
476
|
-
|
|
558
|
+
/** `contexts` é interno ao SDK (ex.: `contexts.run` com o print do captureException) — o evento ainda pode sobrescrever. */
|
|
559
|
+
captureException(error, hint, contexts) {
|
|
560
|
+
if (typeof error === "object" && error !== null) captured.add(error);
|
|
561
|
+
return this.captureEvent(eventFromException(error, hint?.type ?? "error"), hint, contexts);
|
|
477
562
|
}
|
|
478
|
-
captureEvent(event, hint) {
|
|
479
|
-
const final = this.prepareEvent(event, hint);
|
|
563
|
+
captureEvent(event, hint, contexts) {
|
|
564
|
+
const final = this.prepareEvent(contexts ? { ...event, contexts: { ...contexts, ...event.contexts } } : event, hint);
|
|
480
565
|
this.transport.enqueue({ type: "event", payload: final });
|
|
481
566
|
return final.event_id;
|
|
482
567
|
}
|
|
@@ -499,14 +584,15 @@ var Client = class {
|
|
|
499
584
|
sendTransaction(payload) {
|
|
500
585
|
let steps = payload.steps;
|
|
501
586
|
if (steps.length > MAX_STEPS) {
|
|
502
|
-
this.logger.warn(`transaction com ${steps.length}
|
|
587
|
+
this.logger.warn(`transaction com ${steps.length} etapas \u2014 enviando as primeiras ${MAX_STEPS}`);
|
|
503
588
|
steps = steps.slice(0, MAX_STEPS);
|
|
504
589
|
}
|
|
590
|
+
steps = steps.map(clipStep);
|
|
505
591
|
this.transport.enqueue({
|
|
506
592
|
type: "transaction",
|
|
507
593
|
payload: {
|
|
508
594
|
...payload,
|
|
509
|
-
name:
|
|
595
|
+
name: clip(payload.name || "(sem nome)", MAX_TRANSACTION_NAME_CHARS),
|
|
510
596
|
steps,
|
|
511
597
|
release: payload.release ?? this.options.release,
|
|
512
598
|
environment: this.resolveEnvironment(payload.environment),
|
|
@@ -580,12 +666,12 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
580
666
|
const origin = fromOption ? "clientSecret" : `env ${CLIENT_SECRET_ENV}`;
|
|
581
667
|
if (!key) {
|
|
582
668
|
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
|
|
669
|
+
`${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
670
|
);
|
|
585
671
|
}
|
|
586
672
|
if (!SECRET_PATTERN.test(key)) {
|
|
587
673
|
throw new Error(
|
|
588
|
-
`${prefix} ${origin} com formato inv\xE1lido (${key.length} caracteres) \u2014 esperado "hb_" + 64 hex; copie o Client Secret
|
|
674
|
+
`${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
675
|
);
|
|
590
676
|
}
|
|
591
677
|
const baseUrl = validateBaseUrl(envBaseUrl() ?? DEFAULT_BASE_URL, prefix);
|
|
@@ -597,31 +683,6 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
597
683
|
};
|
|
598
684
|
}
|
|
599
685
|
|
|
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
686
|
// src/heartbeat.ts
|
|
626
687
|
var DEFAULT_INTERVAL_MS = WIRE_LIMITS.HEARTBEAT_DEFAULT_INTERVAL_MS;
|
|
627
688
|
var MIN_REPORTED_INTERVAL_MS = WIRE_LIMITS.HEARTBEAT_MIN_INTERVAL_MS;
|
|
@@ -983,6 +1044,42 @@ var TasksApi = class {
|
|
|
983
1044
|
created_at: typeof body.created_at === "string" ? body.created_at : (/* @__PURE__ */ new Date()).toISOString()
|
|
984
1045
|
};
|
|
985
1046
|
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Anexa o processo a uma run já reivindicada para esta instância (env HOBOTS_RUN_ID):
|
|
1049
|
+
* POST /v1/runs/:id/start. Um request só — quem decide o retry é o AttachedRunner.
|
|
1050
|
+
* 404 (não existe) e 409 (cancelada, já iniciada ou de outra instância) viram
|
|
1051
|
+
* TasksApiError com o status.
|
|
1052
|
+
*/
|
|
1053
|
+
async startRun(runId, request, signal) {
|
|
1054
|
+
const { signal: timeoutSignal, cleanup } = withTimeoutSignal(UPDATE_TIMEOUT_MS, signal);
|
|
1055
|
+
let response;
|
|
1056
|
+
try {
|
|
1057
|
+
response = await fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/start`, {
|
|
1058
|
+
redirect: "error",
|
|
1059
|
+
method: "POST",
|
|
1060
|
+
headers: this.headers(),
|
|
1061
|
+
body: JSON.stringify(request),
|
|
1062
|
+
signal: timeoutSignal
|
|
1063
|
+
});
|
|
1064
|
+
} finally {
|
|
1065
|
+
cleanup();
|
|
1066
|
+
}
|
|
1067
|
+
if (!response.ok) {
|
|
1068
|
+
throw new TasksApiError(`in\xEDcio da run falhou (HTTP ${response.status})`, response.status);
|
|
1069
|
+
}
|
|
1070
|
+
const body = await response.json().catch(() => null);
|
|
1071
|
+
if (typeof body?.id !== "string") {
|
|
1072
|
+
throw new TasksApiError("resposta inv\xE1lida do in\xEDcio da run (id ausente)", 0);
|
|
1073
|
+
}
|
|
1074
|
+
const form = body.form;
|
|
1075
|
+
return {
|
|
1076
|
+
id: body.id,
|
|
1077
|
+
params: body.params && typeof body.params === "object" && !Array.isArray(body.params) ? body.params : {},
|
|
1078
|
+
form: typeof form?.id === "string" && typeof form.name === "string" ? { id: form.id, name: form.name } : null,
|
|
1079
|
+
source: typeof body.source === "string" ? body.source : "hobots",
|
|
1080
|
+
created_at: typeof body.created_at === "string" ? body.created_at : (/* @__PURE__ */ new Date()).toISOString()
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
986
1083
|
/** Nunca confia no shape da resposta: runs malformadas são descartadas e o `max` é respeitado. */
|
|
987
1084
|
parsePollResponse(body, max) {
|
|
988
1085
|
const runs = body?.runs;
|
|
@@ -1240,7 +1337,7 @@ var Backoff = class {
|
|
|
1240
1337
|
this.authFailures++;
|
|
1241
1338
|
if (this.authFailures === 1 || this.authFailures % 10 === 0) {
|
|
1242
1339
|
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
|
|
1340
|
+
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
1341
|
);
|
|
1245
1342
|
}
|
|
1246
1343
|
return Math.max(this.floorMs, AUTH_FAILURE_INTERVAL_MS);
|
|
@@ -1266,6 +1363,9 @@ var TERMINAL_ERROR_NAMES = /* @__PURE__ */ new Set(["RunCanceled", "AbortError",
|
|
|
1266
1363
|
function isTerminalError(error) {
|
|
1267
1364
|
return error instanceof Error && TERMINAL_ERROR_NAMES.has(error.name);
|
|
1268
1365
|
}
|
|
1366
|
+
function isCancellation(error) {
|
|
1367
|
+
return error instanceof Error && (error.name === "RunCanceled" || error.name === "AbortError");
|
|
1368
|
+
}
|
|
1269
1369
|
|
|
1270
1370
|
// src/tasks/each.ts
|
|
1271
1371
|
function thrownMessage(error) {
|
|
@@ -1292,11 +1392,11 @@ var ItemHandle = class {
|
|
|
1292
1392
|
}
|
|
1293
1393
|
failed(message, payload, options) {
|
|
1294
1394
|
this.record("failed", message, payload, options?.image);
|
|
1295
|
-
if (this.step && this.step.status !== "error") this.step.
|
|
1395
|
+
if (this.step && this.step.status !== "error") this.step._setStatus("error", message);
|
|
1296
1396
|
}
|
|
1297
1397
|
occurrence(message, payload, options) {
|
|
1298
1398
|
this.record("occurrence", message, payload, options?.image);
|
|
1299
|
-
if (this.step && this.step.status === "ok") this.step.
|
|
1399
|
+
if (this.step && this.step.status === "ok") this.step._setStatus("warning", message);
|
|
1300
1400
|
}
|
|
1301
1401
|
record(status, message, payload, image3) {
|
|
1302
1402
|
this.reported = true;
|
|
@@ -1327,24 +1427,24 @@ async function eachItem(list, handler, options, sink) {
|
|
|
1327
1427
|
const step = sink.startStep(options.op ?? "item", description);
|
|
1328
1428
|
const handle = new ItemHandle(index, id, step, options.payload?.(element, index), sink);
|
|
1329
1429
|
try {
|
|
1330
|
-
await handler(element, handle, index);
|
|
1430
|
+
await sink.stepScope(step, async () => handler(element, handle, index));
|
|
1331
1431
|
if (!handle.reported) handle.succeeded();
|
|
1332
1432
|
} catch (error) {
|
|
1333
1433
|
if (isTerminalError(error) || sink.isAborted()) {
|
|
1334
|
-
step?.
|
|
1335
|
-
step?.finish();
|
|
1434
|
+
step?._setStatus("cancelled")._close();
|
|
1336
1435
|
throw error;
|
|
1337
1436
|
}
|
|
1338
1437
|
const message = thrownMessage(error);
|
|
1339
1438
|
if (!handle.reported) handle.failed(message);
|
|
1340
|
-
step?.
|
|
1439
|
+
step?._failed(error);
|
|
1341
1440
|
sink.logError(`item ${description} falhou: ${message}`);
|
|
1342
1441
|
}
|
|
1343
|
-
step?.
|
|
1442
|
+
step?._close();
|
|
1344
1443
|
if (options.progress !== false) sink.advance();
|
|
1345
1444
|
result.total += 1;
|
|
1346
1445
|
result[handle.status ?? "succeeded"] += 1;
|
|
1347
1446
|
index += 1;
|
|
1447
|
+
if (options.flush !== false) await sink.flush();
|
|
1348
1448
|
}
|
|
1349
1449
|
return result;
|
|
1350
1450
|
}
|
|
@@ -1384,6 +1484,11 @@ function isInsideDir(root, candidate, sep) {
|
|
|
1384
1484
|
function unavailableLocal(ref) {
|
|
1385
1485
|
return new Error(`arquivo "${ref.name}" enviado pelo Studio de outro computador ou j\xE1 consumido`);
|
|
1386
1486
|
}
|
|
1487
|
+
function localRunPath(ref) {
|
|
1488
|
+
if (!isLocalRun()) return void 0;
|
|
1489
|
+
const path = ref.path;
|
|
1490
|
+
return typeof path === "string" && path.trim() !== "" ? path : void 0;
|
|
1491
|
+
}
|
|
1387
1492
|
function errorCode(error) {
|
|
1388
1493
|
return error?.code;
|
|
1389
1494
|
}
|
|
@@ -1551,8 +1656,16 @@ var RunFileStore = class {
|
|
|
1551
1656
|
this.deps.warn(`arquivo "${ref.name}" baixado, mas a confirma\xE7\xE3o ao servidor falhou: ${String(error)}`);
|
|
1552
1657
|
}
|
|
1553
1658
|
}
|
|
1554
|
-
/**
|
|
1659
|
+
/**
|
|
1660
|
+
* storage 'local': copia o único arquivo de `$HOBOTS_LOCAL_FILES_DIR/<id>/` e apaga a pasta de
|
|
1661
|
+
* origem. No modo local, um FileRef com `path` copia direto desse caminho, sem apagar a origem.
|
|
1662
|
+
*/
|
|
1555
1663
|
async copyLocal(ref, target) {
|
|
1664
|
+
const direct = localRunPath(ref);
|
|
1665
|
+
if (direct !== void 0) {
|
|
1666
|
+
await this.copyFromPath(ref, direct, target);
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1556
1669
|
const base = process.env[LOCAL_FILES_DIR_ENV];
|
|
1557
1670
|
if (!base) throw unavailableLocal(ref);
|
|
1558
1671
|
const { copyFile, readdir, rename, rm, stat } = await import("fs/promises");
|
|
@@ -1586,6 +1699,26 @@ var RunFileStore = class {
|
|
|
1586
1699
|
}
|
|
1587
1700
|
await rm(folder, { recursive: true, force: true });
|
|
1588
1701
|
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Modo local: cópia do arquivo apontado por `path` (teste roda várias vezes — a fonte fica).
|
|
1704
|
+
* Sem conferência de tamanho: o `size` do FileRef é informativo aqui.
|
|
1705
|
+
*/
|
|
1706
|
+
async copyFromPath(ref, path, target) {
|
|
1707
|
+
const { copyFile, rename, rm, stat } = await import("fs/promises");
|
|
1708
|
+
const { resolve: resolve2 } = await import("path");
|
|
1709
|
+
const source = resolve2(path);
|
|
1710
|
+
const info = await stat(source).catch(() => void 0);
|
|
1711
|
+
if (!info?.isFile()) throw new Error(`arquivo "${ref.name}" n\xE3o encontrado em ${path}`);
|
|
1712
|
+
this.checkCanceled();
|
|
1713
|
+
const part = `${target}.part`;
|
|
1714
|
+
try {
|
|
1715
|
+
await copyFile(source, part);
|
|
1716
|
+
await rename(part, target);
|
|
1717
|
+
} catch (error) {
|
|
1718
|
+
await rm(part, { force: true });
|
|
1719
|
+
throw error;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1589
1722
|
translate(error, ref) {
|
|
1590
1723
|
if (this.deps.signal.aborted) return runCanceledError();
|
|
1591
1724
|
if (error instanceof TasksApiError && error.status === 410) {
|
|
@@ -1714,6 +1847,11 @@ function printRunLog(level, message) {
|
|
|
1714
1847
|
const method2 = level === "warning" ? "warn" : level === "success" ? "info" : level;
|
|
1715
1848
|
console[method2](line, message);
|
|
1716
1849
|
}
|
|
1850
|
+
var PROGRESS_LINE_PREFIX = "[hobots:progress]";
|
|
1851
|
+
var PROGRESS_PRINT_THROTTLE_MS = 250;
|
|
1852
|
+
function printRunProgress(current, total) {
|
|
1853
|
+
console.log(`${PROGRESS_LINE_PREFIX} ${current}/${total}`);
|
|
1854
|
+
}
|
|
1717
1855
|
function cancelableSleep(ms, signal) {
|
|
1718
1856
|
return new Promise((resolve2, reject) => {
|
|
1719
1857
|
if (signal.aborted) {
|
|
@@ -1782,14 +1920,16 @@ var RunReporter = class {
|
|
|
1782
1920
|
tx.setStepGuard(checkCanceled);
|
|
1783
1921
|
const eachSink = {
|
|
1784
1922
|
report: (status, message, payload, id, image3) => this.addItem(status, message, payload, id, image3),
|
|
1785
|
-
startStep: (op, description) => tx.
|
|
1923
|
+
startStep: (op, description) => tx.start(op, description),
|
|
1924
|
+
stepScope: (step, fn) => tx.scope(step, fn),
|
|
1786
1925
|
declareTotal: (total) => {
|
|
1787
1926
|
if (this.progressTotal == null) this.setProgress(this.progressCurrent, total);
|
|
1788
1927
|
},
|
|
1789
1928
|
advance: () => this.setProgress(this.progressCurrent + 1, this.progressTotal),
|
|
1790
1929
|
checkCanceled,
|
|
1791
1930
|
isAborted: () => this.abortController.signal.aborted,
|
|
1792
|
-
logError: (message) => this.log("error", message)
|
|
1931
|
+
logError: (message) => this.log("error", message),
|
|
1932
|
+
flush: () => this.flush()
|
|
1793
1933
|
};
|
|
1794
1934
|
const items = {
|
|
1795
1935
|
add: (item) => {
|
|
@@ -1844,7 +1984,14 @@ var RunReporter = class {
|
|
|
1844
1984
|
items,
|
|
1845
1985
|
progress,
|
|
1846
1986
|
files: this.fileStore.files,
|
|
1847
|
-
|
|
1987
|
+
step: async (name, fn, options) => {
|
|
1988
|
+
const root = tx.currentStep() === void 0;
|
|
1989
|
+
try {
|
|
1990
|
+
return await tx.step(name, fn, { ...options, signal, wait: sleep2, warn: (message) => this.log("warning", message) });
|
|
1991
|
+
} finally {
|
|
1992
|
+
if (options?.flush !== false && root && !signal.aborted) await this.flush();
|
|
1993
|
+
}
|
|
1994
|
+
},
|
|
1848
1995
|
flush: (timeoutMs) => this.flush(timeoutMs),
|
|
1849
1996
|
signal,
|
|
1850
1997
|
throwIfCanceled: checkCanceled,
|
|
@@ -1875,8 +2022,17 @@ var RunReporter = class {
|
|
|
1875
2022
|
itemSeq = 0;
|
|
1876
2023
|
/** Servidor sem o recurso (404) — para de tentar (e de avisar) nesta run. */
|
|
1877
2024
|
itemsUnsupported = false;
|
|
1878
|
-
/**
|
|
2025
|
+
/**
|
|
2026
|
+
* Cota de itens por run do servidor (429, legado — hoje a api não tem teto de itens) —
|
|
2027
|
+
* degrada com um aviso só.
|
|
2028
|
+
*/
|
|
1879
2029
|
itemsQuotaExceeded = false;
|
|
2030
|
+
/**
|
|
2031
|
+
* Armazenamento do plano cheio (402 `storage_limit`): o lote é descartado e o aviso sai
|
|
2032
|
+
* uma vez por episódio. Os envios continuam — num agente `start()` o espaço pode ser
|
|
2033
|
+
* liberado (ou o plano trocado) sem reiniciar; um lote aceito rearma o aviso.
|
|
2034
|
+
*/
|
|
2035
|
+
itemsStorageFull = false;
|
|
1880
2036
|
/**
|
|
1881
2037
|
* Só o kill() do exit path fecha os itens — `dead` (run cancelada/finalizada no
|
|
1882
2038
|
* servidor) NÃO fecha: o servidor aceita itens de run terminal (trabalho já feito).
|
|
@@ -1898,6 +2054,8 @@ var RunReporter = class {
|
|
|
1898
2054
|
progressDirty = false;
|
|
1899
2055
|
progressTimer;
|
|
1900
2056
|
progressWarned = false;
|
|
2057
|
+
/** Relógio do último eco `[hobots:progress]` no terminal (throttle do eco, não do wire). */
|
|
2058
|
+
progressLastPrint = 0;
|
|
1901
2059
|
/** Arquivos do formulário desta run (`ctx.files`) — a pasta temporária é apagada no fim. */
|
|
1902
2060
|
fileStore;
|
|
1903
2061
|
/** Apaga as cópias de arquivo que ficaram na pasta temporária da run. Nunca lança. */
|
|
@@ -1948,15 +2106,17 @@ var RunReporter = class {
|
|
|
1948
2106
|
/**
|
|
1949
2107
|
* Linha de log da run com eco no terminal. `image` (opcional) vira o anexo da linha:
|
|
1950
2108
|
* o id entra na entrada ANTES de qualquer I/O — é isso que mantém a ordem dos logs
|
|
1951
|
-
* intacta e o handler livre da latência do upload.
|
|
2109
|
+
* intacta e o handler livre da latência do upload. Devolve o id do anexo (quando houve
|
|
2110
|
+
* imagem e ela não foi descartada) — o `captureException` o leva ao evento da issue.
|
|
1952
2111
|
*/
|
|
1953
2112
|
log(level, message, image3) {
|
|
1954
|
-
if (this.dead) return;
|
|
2113
|
+
if (this.dead) return void 0;
|
|
1955
2114
|
const scrubbed = scrubSecrets(safeString(message));
|
|
1956
2115
|
const ts = nowISO();
|
|
1957
2116
|
const attachmentId = image3 === void 0 ? void 0 : this.queueAttachment(image3, ts);
|
|
1958
2117
|
printRunLog(level, attachmentId ? `${scrubbed} [imagem]` : scrubbed);
|
|
1959
2118
|
this.push(level, scrubbed, true, attachmentId ? [attachmentId] : void 0, ts);
|
|
2119
|
+
return attachmentId;
|
|
1960
2120
|
}
|
|
1961
2121
|
/** Bufferiza uma entrada de log sem eco no terminal (cap, truncate, flush). */
|
|
1962
2122
|
push(level, message, alreadyScrubbed = false, attachments, ts = nowISO()) {
|
|
@@ -2002,7 +2162,7 @@ var RunReporter = class {
|
|
|
2002
2162
|
}
|
|
2003
2163
|
}
|
|
2004
2164
|
/**
|
|
2005
|
-
* `hobots.flush()`: o bot decide quando entregar o que já fez —
|
|
2165
|
+
* `hobots.flush()`: o bot decide quando entregar o que já fez — etapas raiz finalizadas
|
|
2006
2166
|
* viram transactions parciais da run, e itens, logs e progresso pendentes saem já, sem
|
|
2007
2167
|
* esperar o throttle. Aguarda a entrega com teto. Nunca lança (pode ir num `finally`).
|
|
2008
2168
|
*/
|
|
@@ -2060,10 +2220,16 @@ var RunReporter = class {
|
|
|
2060
2220
|
}
|
|
2061
2221
|
const safeCurrent = Number.isFinite(current) ? Math.max(0, Math.floor(current)) : this.progressCurrent;
|
|
2062
2222
|
const next = total == null ? safeCurrent : Math.min(safeCurrent, total);
|
|
2063
|
-
|
|
2223
|
+
const totalChanged = total !== this.progressTotal;
|
|
2224
|
+
if (next === this.progressCurrent && !totalChanged) return;
|
|
2064
2225
|
this.progressCurrent = next;
|
|
2065
2226
|
this.progressTotal = total;
|
|
2066
2227
|
if (total == null) return;
|
|
2228
|
+
const now = Date.now();
|
|
2229
|
+
if (totalChanged || next >= total || now - this.progressLastPrint >= PROGRESS_PRINT_THROTTLE_MS) {
|
|
2230
|
+
this.progressLastPrint = now;
|
|
2231
|
+
printRunProgress(next, total);
|
|
2232
|
+
}
|
|
2067
2233
|
this.progressDirty = true;
|
|
2068
2234
|
if (this.progressTimer) return;
|
|
2069
2235
|
this.progressTimer = setTimeout(() => {
|
|
@@ -2186,6 +2352,7 @@ var RunReporter = class {
|
|
|
2186
2352
|
if (this.itemsUnsupported) return;
|
|
2187
2353
|
try {
|
|
2188
2354
|
await this.api.createItems(this.runId, { items: batch });
|
|
2355
|
+
this.itemsStorageFull = false;
|
|
2189
2356
|
} catch (error) {
|
|
2190
2357
|
if (error instanceof TasksApiError && error.status === 404) {
|
|
2191
2358
|
this.itemsUnsupported = true;
|
|
@@ -2199,6 +2366,13 @@ var RunReporter = class {
|
|
|
2199
2366
|
}
|
|
2200
2367
|
return;
|
|
2201
2368
|
}
|
|
2369
|
+
if (error instanceof TasksApiError && error.status === 402) {
|
|
2370
|
+
if (!this.itemsStorageFull) {
|
|
2371
|
+
this.itemsStorageFull = true;
|
|
2372
|
+
this.log("warning", "itens descartados: armazenamento do plano cheio \u2014 libere espa\xE7o ou fa\xE7a upgrade em Plano e cobran\xE7a");
|
|
2373
|
+
}
|
|
2374
|
+
return;
|
|
2375
|
+
}
|
|
2202
2376
|
if (error instanceof TasksApiError) {
|
|
2203
2377
|
this.logger.error(`lote de ${batch.length} item(ns) rejeitado pelo servidor:`, error);
|
|
2204
2378
|
this.log("warning", `${batch.length} item(ns) descartado(s): envio rejeitado pelo servidor (HTTP ${error.status})`);
|
|
@@ -2230,7 +2404,7 @@ var RunReporter = class {
|
|
|
2230
2404
|
}
|
|
2231
2405
|
// ── Anexos ───────────────────────────────────────────────────────────────────
|
|
2232
2406
|
/**
|
|
2233
|
-
* Pendura uma imagem num evento da run (log, item ou
|
|
2407
|
+
* Pendura uma imagem num evento da run (log, item ou etapa): faz as checagens que não
|
|
2234
2408
|
* dependem de I/O (reporter morto, instalação sem anexos, cota), reserva o id e dispara
|
|
2235
2409
|
* o upload em segundo plano. Devolve o id — já referenciável pelo evento — ou undefined
|
|
2236
2410
|
* quando a imagem foi descartada (sempre com aviso no log da run). Nunca lança: todo
|
|
@@ -2403,11 +2577,13 @@ var RunReporter = class {
|
|
|
2403
2577
|
|
|
2404
2578
|
// src/tasks/run-context-store.ts
|
|
2405
2579
|
var storage;
|
|
2580
|
+
var stepStorage;
|
|
2406
2581
|
var initPromise;
|
|
2407
2582
|
async function ensureStorage() {
|
|
2408
2583
|
if (storage || !isNode()) return;
|
|
2409
2584
|
initPromise ??= import("async_hooks").then(({ AsyncLocalStorage: ALS }) => {
|
|
2410
2585
|
storage = new ALS();
|
|
2586
|
+
stepStorage = new ALS();
|
|
2411
2587
|
});
|
|
2412
2588
|
await initPromise;
|
|
2413
2589
|
}
|
|
@@ -2418,12 +2594,23 @@ async function runWithRunContext(store, fn) {
|
|
|
2418
2594
|
function currentRunReporter() {
|
|
2419
2595
|
return storage?.getStore()?.reporter;
|
|
2420
2596
|
}
|
|
2597
|
+
async function runWithStepScope(scope, fn) {
|
|
2598
|
+
await ensureStorage();
|
|
2599
|
+
return stepStorage ? stepStorage.run(scope, fn) : fn();
|
|
2600
|
+
}
|
|
2601
|
+
function currentStep(transaction) {
|
|
2602
|
+
const scope = stepStorage?.getStore();
|
|
2603
|
+
return scope?.transaction === transaction ? scope.step : void 0;
|
|
2604
|
+
}
|
|
2421
2605
|
|
|
2422
2606
|
// src/transaction.ts
|
|
2423
2607
|
var FLUSH_TRANSACTION_OP = "hobots.run.flush";
|
|
2608
|
+
var DEFAULT_STEP_RETRIES = 1;
|
|
2609
|
+
var DEFAULT_STEP_RETRY_DELAY_MS = 5e3;
|
|
2424
2610
|
var MAX_STEP_MESSAGE_CHARS = 2e3;
|
|
2611
|
+
var MAX_BACKLOG = 100;
|
|
2425
2612
|
function stepMessage(text) {
|
|
2426
|
-
return
|
|
2613
|
+
return clip(scrubSecrets(text), MAX_STEP_MESSAGE_CHARS);
|
|
2427
2614
|
}
|
|
2428
2615
|
function stepId() {
|
|
2429
2616
|
return uuid4().slice(0, 16);
|
|
@@ -2431,6 +2618,13 @@ function stepId() {
|
|
|
2431
2618
|
function stepLabel(op, description) {
|
|
2432
2619
|
return [op, description].filter(Boolean).join(" \u2014 ") || "(sem nome)";
|
|
2433
2620
|
}
|
|
2621
|
+
function errorText(error) {
|
|
2622
|
+
if (error instanceof Error) return error.message || error.name;
|
|
2623
|
+
return safeString(error);
|
|
2624
|
+
}
|
|
2625
|
+
function errorReason(error) {
|
|
2626
|
+
return error instanceof Error ? `${error.name}: ${errorText(error)}` : errorText(error);
|
|
2627
|
+
}
|
|
2434
2628
|
function levelForStatus(status) {
|
|
2435
2629
|
switch (status) {
|
|
2436
2630
|
case "warning":
|
|
@@ -2470,58 +2664,68 @@ var Step = class {
|
|
|
2470
2664
|
startTimestamp = nowISO();
|
|
2471
2665
|
status = "ok";
|
|
2472
2666
|
endTimestamp;
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
* `options.image` pendura um print do estado da tela em `data.attachments`.
|
|
2478
|
-
*/
|
|
2479
|
-
setStatus(status, message, options) {
|
|
2480
|
-
this.status = status;
|
|
2481
|
-
if (message) this.setData("message", stepMessage(message));
|
|
2667
|
+
_data;
|
|
2668
|
+
// ── API do bot ────────────────────────────────────────────────────────────
|
|
2669
|
+
warning(message, options) {
|
|
2670
|
+
if (this.status !== "error") this._setStatus("warning", message);
|
|
2482
2671
|
if (options?.image !== void 0) this.attachImage(options.image);
|
|
2483
2672
|
return this;
|
|
2484
2673
|
}
|
|
2674
|
+
image(image3) {
|
|
2675
|
+
this.attachImage(image3);
|
|
2676
|
+
return this;
|
|
2677
|
+
}
|
|
2678
|
+
data(key, value) {
|
|
2679
|
+
if (key === "attachments") return this;
|
|
2680
|
+
this._setData(key, value);
|
|
2681
|
+
return this;
|
|
2682
|
+
}
|
|
2683
|
+
// ── Internos (chamados pelo SDK) ──────────────────────────────────────────
|
|
2684
|
+
/** `data` da etapa como vai no wire (undefined quando vazio). */
|
|
2685
|
+
get payload() {
|
|
2686
|
+
return this._data;
|
|
2687
|
+
}
|
|
2688
|
+
_setStatus(status, message) {
|
|
2689
|
+
this.status = status;
|
|
2690
|
+
if (message) this._setData("message", stepMessage(safeString(message)));
|
|
2691
|
+
return this;
|
|
2692
|
+
}
|
|
2485
2693
|
/**
|
|
2486
|
-
* Marca
|
|
2487
|
-
*
|
|
2488
|
-
* `options.image` pendura um print do estado da tela em `data.attachments`.
|
|
2694
|
+
* Marca `error` guardando `{ type, message }` em `data.error` e fecha a etapa. Aceita qualquer
|
|
2695
|
+
* valor lançado; `message` sobrescreve a mensagem do erro. Numa etapa já fechada não faz nada.
|
|
2489
2696
|
*/
|
|
2490
|
-
|
|
2697
|
+
_failed(error, message) {
|
|
2698
|
+
if (this.endTimestamp) return this;
|
|
2491
2699
|
this.status = "error";
|
|
2492
2700
|
const type = error instanceof Error ? error.name : typeof error === "object" && error !== null ? "Error" : void 0;
|
|
2493
|
-
const raw = message ?? (error
|
|
2494
|
-
this.
|
|
2495
|
-
|
|
2701
|
+
const raw = message ?? errorText(error);
|
|
2702
|
+
this._setData("error", { ...type ? { type } : {}, message: stepMessage(safeString(raw)) });
|
|
2703
|
+
this._close();
|
|
2496
2704
|
return this;
|
|
2497
2705
|
}
|
|
2706
|
+
_setData(key, value) {
|
|
2707
|
+
this._data = { ...this._data, [key]: value };
|
|
2708
|
+
}
|
|
2498
2709
|
/**
|
|
2499
2710
|
* Dispara o upload pela run (sink do RunReporter) e referencia o anexo em
|
|
2500
2711
|
* `data.attachments` (chave reservada; teto igual ao das linhas de log). Fora de uma
|
|
2501
2712
|
* run não há onde pendurar a imagem — descarta com aviso único.
|
|
2502
2713
|
*/
|
|
2503
2714
|
attachImage(image3) {
|
|
2504
|
-
const current = Array.isArray(this.
|
|
2715
|
+
const current = Array.isArray(this._data?.attachments) ? this._data.attachments : [];
|
|
2505
2716
|
if (current.length >= MAX_ATTACHMENTS_PER_LOG_ENTRY) return;
|
|
2506
2717
|
const id = this.transaction.attachImage(image3);
|
|
2507
2718
|
if (!id) return;
|
|
2508
|
-
this.
|
|
2719
|
+
this._setData("attachments", [...current, id]);
|
|
2509
2720
|
}
|
|
2510
|
-
/** Motivo registrado
|
|
2721
|
+
/** Motivo registrado (warning/erro) — usado no log de FINISH. */
|
|
2511
2722
|
reason() {
|
|
2512
|
-
const error = this.
|
|
2513
|
-
const text = typeof error?.message === "string" ? error.message : this.
|
|
2723
|
+
const error = this._data?.error;
|
|
2724
|
+
const text = typeof error?.message === "string" ? error.message : this._data?.message;
|
|
2514
2725
|
return typeof text === "string" && text ? text : void 0;
|
|
2515
2726
|
}
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
return this;
|
|
2519
|
-
}
|
|
2520
|
-
/** Step aninhada (filha desta step). */
|
|
2521
|
-
startChild(op, description) {
|
|
2522
|
-
return this.transaction.createStep(this.stepId, op, description);
|
|
2523
|
-
}
|
|
2524
|
-
finish() {
|
|
2727
|
+
/** Fecha a etapa mantendo o status que estiver. Idempotente. */
|
|
2728
|
+
_close() {
|
|
2525
2729
|
if (this.endTimestamp) return;
|
|
2526
2730
|
this.endTimestamp = nowISO();
|
|
2527
2731
|
this.transaction.emit(
|
|
@@ -2537,22 +2741,23 @@ var Step = class {
|
|
|
2537
2741
|
toJSON() {
|
|
2538
2742
|
return {
|
|
2539
2743
|
step_id: this.stepId,
|
|
2540
|
-
parent_step_id: this.parentStepId,
|
|
2541
|
-
op: this.op,
|
|
2542
|
-
description: this.description,
|
|
2744
|
+
...this.parentStepId ? { parent_step_id: this.parentStepId } : {},
|
|
2745
|
+
...this.op ? { op: clip(this.op, MAX_STEP_OP_CHARS) } : {},
|
|
2746
|
+
...this.description ? { description: clip(this.description, MAX_STEP_DESCRIPTION_CHARS) } : {},
|
|
2543
2747
|
status: this.status,
|
|
2544
2748
|
start_timestamp: this.startTimestamp,
|
|
2545
2749
|
timestamp: this.endTimestamp ?? nowISO(),
|
|
2546
|
-
data: this.
|
|
2750
|
+
...this._data ? { data: this._data } : {}
|
|
2547
2751
|
};
|
|
2548
2752
|
}
|
|
2549
2753
|
};
|
|
2550
2754
|
var Transaction = class {
|
|
2551
|
-
constructor(client, name, op, runId) {
|
|
2755
|
+
constructor(client, name, op, runId, options = {}) {
|
|
2552
2756
|
this.client = client;
|
|
2553
2757
|
this.name = name;
|
|
2554
2758
|
this.op = op;
|
|
2555
2759
|
this.runId = runId;
|
|
2760
|
+
this.echo = options.echo ?? false;
|
|
2556
2761
|
this.emit("transaction", "start", op ? `${name} (${op})` : name);
|
|
2557
2762
|
}
|
|
2558
2763
|
client;
|
|
@@ -2564,33 +2769,36 @@ var Transaction = class {
|
|
|
2564
2769
|
startTimestamp = nowISO();
|
|
2565
2770
|
status = "ok";
|
|
2566
2771
|
steps = [];
|
|
2567
|
-
tags = {};
|
|
2568
2772
|
finished = false;
|
|
2569
2773
|
lifecycleSink;
|
|
2570
2774
|
attachmentSink;
|
|
2571
2775
|
warnedNoAttachmentSink = false;
|
|
2572
|
-
// instalado pelo RunReporter: abrir uma
|
|
2776
|
+
// instalado pelo RunReporter: abrir uma etapa é um ponto de parada cooperativo (lança RunCanceled)
|
|
2573
2777
|
stepGuard;
|
|
2778
|
+
// modo local (sem client): o FINISH de cada etapa sai no terminal, como `[hobots:item]`
|
|
2779
|
+
echo;
|
|
2574
2780
|
// eventos emitidos antes do sink existir (o START da transaction sai no construtor,
|
|
2575
2781
|
// antes do RunReporter conectar) — replay no setLifecycleSink
|
|
2576
2782
|
lifecycleBacklog = [];
|
|
2783
|
+
// ── Lifecycle / eco ───────────────────────────────────────────────────────
|
|
2577
2784
|
/**
|
|
2578
|
-
* Registra o início/fim de uma
|
|
2579
|
-
* lifecycle, se conectado (é assim que os eventos de performance aparecem nos
|
|
2580
|
-
*
|
|
2581
|
-
*
|
|
2785
|
+
* Registra o início/fim de uma etapa ou da transaction: espelha a linha no sink de
|
|
2786
|
+
* lifecycle, se conectado (é assim que os eventos de performance aparecem nos logs da
|
|
2787
|
+
* run no app), e ecoa no terminal com `debug: true` — ou, no modo local, só o FINISH das
|
|
2788
|
+
* etapas (`[hobots:step] FINISH …`), para o Studio e o dev verem.
|
|
2582
2789
|
*/
|
|
2583
2790
|
emit(kind, phase, label, startTs, endTs, status, reason) {
|
|
2584
2791
|
const durationMs = startTs && endTs ? new Date(endTs).getTime() - new Date(startTs).getTime() : void 0;
|
|
2585
2792
|
const suffix = (durationMs != null ? ` (${durationMs}ms)` : "") + (reason ? `: ${reason}` : "");
|
|
2586
|
-
|
|
2793
|
+
const localFinish = this.echo && kind === "step" && phase === "finish";
|
|
2794
|
+
if (this.client?.debug || localFinish) {
|
|
2587
2795
|
const tag = colorize(`[hobots:${kind}] ${phase.toUpperCase()}`, phase === "finish" ? colorForStatus(status ?? "ok") : "cyan");
|
|
2588
2796
|
console.log(`${tag} ${label}${suffix}`);
|
|
2589
2797
|
}
|
|
2590
2798
|
const level = phase === "finish" ? levelForStatus(status ?? "ok") : "info";
|
|
2591
2799
|
const message = `[${kind}] ${phase.toUpperCase()} ${label}${suffix}`;
|
|
2592
2800
|
if (this.lifecycleSink) this.lifecycleSink(level, message);
|
|
2593
|
-
else if (this.lifecycleBacklog.length <
|
|
2801
|
+
else if (this.lifecycleBacklog.length < MAX_BACKLOG) this.lifecycleBacklog.push([level, message]);
|
|
2594
2802
|
}
|
|
2595
2803
|
/** Conecta o espelho de lifecycle (RunReporter) e faz replay do backlog em ordem. */
|
|
2596
2804
|
setLifecycleSink(sink) {
|
|
@@ -2599,59 +2807,111 @@ var Transaction = class {
|
|
|
2599
2807
|
this.lifecycleBacklog = [];
|
|
2600
2808
|
for (const [level, message] of backlog) sink(level, message);
|
|
2601
2809
|
}
|
|
2602
|
-
/** Conecta o pipeline de anexos da run — é o que dá imagem às
|
|
2810
|
+
/** Conecta o pipeline de anexos da run — é o que dá imagem às etapas. */
|
|
2603
2811
|
setAttachmentSink(sink) {
|
|
2604
2812
|
this.attachmentSink = sink;
|
|
2605
2813
|
}
|
|
2606
|
-
/** Imagem de uma
|
|
2814
|
+
/** Imagem de uma etapa → id do anexo pela run; sem run, descarta com aviso único. */
|
|
2607
2815
|
attachImage(image3) {
|
|
2608
2816
|
if (this.attachmentSink) return this.attachmentSink(image3, nowISO());
|
|
2609
2817
|
if (!this.warnedNoAttachmentSink) {
|
|
2610
2818
|
this.warnedNoAttachmentSink = true;
|
|
2611
|
-
console.warn("[hobots] imagem de
|
|
2819
|
+
console.warn("[hobots] imagem de etapa descartada \u2014 s\xF3 \xE9 suportada dentro de uma execu\xE7\xE3o");
|
|
2612
2820
|
}
|
|
2613
2821
|
return void 0;
|
|
2614
2822
|
}
|
|
2823
|
+
// ── Etapas ────────────────────────────────────────────────────────────────
|
|
2615
2824
|
/**
|
|
2616
|
-
* Conecta a checagem de cancelamento chamada antes de cada
|
|
2617
|
-
*
|
|
2825
|
+
* Conecta a checagem de cancelamento chamada antes de cada etapa nova (inclusive antes de
|
|
2826
|
+
* cada nova tentativa) — é assim que `hobots.step()` para uma run cancelada.
|
|
2618
2827
|
*/
|
|
2619
2828
|
setStepGuard(guard) {
|
|
2620
2829
|
this.stepGuard = guard;
|
|
2621
2830
|
}
|
|
2622
|
-
|
|
2831
|
+
/** A etapa aberta por `step()` (ou `items.each`) no contexto async atual, se houver. */
|
|
2832
|
+
currentStep() {
|
|
2833
|
+
return currentStep(this);
|
|
2834
|
+
}
|
|
2835
|
+
/** Faz de `step` a etapa corrente durante `fn` (filhas aninham nela). Sem etapa, só roda `fn`. */
|
|
2836
|
+
scope(step, fn) {
|
|
2837
|
+
if (!step) return fn();
|
|
2838
|
+
return runWithStepScope({ transaction: this, step }, fn);
|
|
2839
|
+
}
|
|
2840
|
+
/** Abre uma etapa sob a corrente (ou direta da transaction). Interno: o bot usa `step()`. */
|
|
2841
|
+
start(op, description) {
|
|
2623
2842
|
this.stepGuard?.();
|
|
2624
|
-
const
|
|
2843
|
+
const parent = this.currentStep();
|
|
2844
|
+
const step = new Step(this, parent ? parent.stepId : this.stepId, op, description);
|
|
2625
2845
|
this.steps.push(step);
|
|
2626
2846
|
this.emit("step", "start", stepLabel(op, description));
|
|
2627
2847
|
return step;
|
|
2628
2848
|
}
|
|
2629
|
-
/**
|
|
2630
|
-
|
|
2631
|
-
|
|
2849
|
+
/**
|
|
2850
|
+
* Registra a etapa `name` executando `fn(etapa)` até dar certo ou esgotar `retries`
|
|
2851
|
+
* tentativas extras (default 1), esperando `retryDelayMs` entre elas (default 5 000).
|
|
2852
|
+
* Resolve com o retorno de `fn`.
|
|
2853
|
+
*
|
|
2854
|
+
* Uma etapa só no waterfall, cobrindo todas as tentativas: `data.attempts` quando houve
|
|
2855
|
+
* mais de uma; `warning` com o motivo quando a última deu certo; `error` (e a exceção
|
|
2856
|
+
* sobe) quando esgotou; `cancelled` em cancelamento/timeout, que nunca repetem.
|
|
2857
|
+
*/
|
|
2858
|
+
async step(name, fn, options = {}) {
|
|
2859
|
+
const requested = options.retries ?? DEFAULT_STEP_RETRIES;
|
|
2860
|
+
const totalRetries = Number.isFinite(requested) ? Math.max(0, Math.floor(requested)) : DEFAULT_STEP_RETRIES;
|
|
2861
|
+
const step = this.start(void 0, safeString(name));
|
|
2862
|
+
let attempts = 0;
|
|
2863
|
+
let lastFailure;
|
|
2864
|
+
try {
|
|
2865
|
+
const result = await retryWithBackoff(
|
|
2866
|
+
(attempt) => {
|
|
2867
|
+
attempts = attempt;
|
|
2868
|
+
return this.scope(step, async () => fn(step));
|
|
2869
|
+
},
|
|
2870
|
+
{
|
|
2871
|
+
retries: totalRetries,
|
|
2872
|
+
delayMs: (attempt) => resolveDelay(options.retryDelayMs, attempt, DEFAULT_STEP_RETRY_DELAY_MS),
|
|
2873
|
+
signal: options.signal,
|
|
2874
|
+
wait: options.wait,
|
|
2875
|
+
onRetry: (error, attempt, delayMs) => {
|
|
2876
|
+
lastFailure = { error };
|
|
2877
|
+
options.warn?.(
|
|
2878
|
+
`etapa "${step.description}": tentativa ${attempt}/${totalRetries + 1} falhou: ${truncate(errorReason(error), 500)} \u2014 nova tentativa em ${delayMs}ms`
|
|
2879
|
+
);
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
);
|
|
2883
|
+
this.noteAttempts(step, attempts);
|
|
2884
|
+
if (lastFailure && step.status === "ok") {
|
|
2885
|
+
step._setStatus("warning", `tentativa ${attempts - 1}/${totalRetries + 1} falhou: ${errorReason(lastFailure.error)}`);
|
|
2886
|
+
}
|
|
2887
|
+
step._close();
|
|
2888
|
+
return result;
|
|
2889
|
+
} catch (error) {
|
|
2890
|
+
this.noteAttempts(step, attempts);
|
|
2891
|
+
if (isTerminalError(error)) step._setStatus("cancelled")._close();
|
|
2892
|
+
else step._failed(error);
|
|
2893
|
+
throw error;
|
|
2894
|
+
}
|
|
2632
2895
|
}
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
return this;
|
|
2896
|
+
noteAttempts(step, attempts) {
|
|
2897
|
+
if (attempts > 1) step._setData("attempts", attempts);
|
|
2636
2898
|
}
|
|
2899
|
+
// ── Metadados ─────────────────────────────────────────────────────────────
|
|
2637
2900
|
setStatus(status) {
|
|
2638
2901
|
this.status = status;
|
|
2639
2902
|
return this;
|
|
2640
2903
|
}
|
|
2641
|
-
|
|
2642
|
-
this.name = name;
|
|
2643
|
-
return this;
|
|
2644
|
-
}
|
|
2904
|
+
// ── Encerramento ──────────────────────────────────────────────────────────
|
|
2645
2905
|
/**
|
|
2646
|
-
* Envia já as
|
|
2906
|
+
* Envia já as etapas raiz finalizadas (e todas as descendentes, fechando as que
|
|
2647
2907
|
* ficaram abertas) como transactions parciais da mesma run (`op` hobots.run.flush,
|
|
2648
|
-
* `event_id` novo, até 200
|
|
2908
|
+
* `event_id` novo, até 200 etapas cada) e as tira da memória. Etapas raiz ainda abertas
|
|
2649
2909
|
* ficam para o próximo flush ou para o `finish()`. É assim que um agente sempre
|
|
2650
2910
|
* ligado (`start()`), cuja run não termina, entrega as etapas enquanto processa.
|
|
2651
|
-
* Devolve quantas
|
|
2911
|
+
* Devolve quantas etapas foram enviadas (0 sem client, sem nada terminado ou depois do `finish()`).
|
|
2652
2912
|
*/
|
|
2653
2913
|
flush() {
|
|
2654
|
-
if (this.finished) return 0;
|
|
2914
|
+
if (!this.client || this.finished) return 0;
|
|
2655
2915
|
const ready = /* @__PURE__ */ new Set();
|
|
2656
2916
|
for (const step of this.steps) {
|
|
2657
2917
|
if (step.parentStepId === this.stepId && step.endTimestamp) ready.add(step.stepId);
|
|
@@ -2667,15 +2927,13 @@ var Transaction = class {
|
|
|
2667
2927
|
remaining.push(step);
|
|
2668
2928
|
}
|
|
2669
2929
|
}
|
|
2670
|
-
for (const step of batch) step.
|
|
2930
|
+
for (const step of batch) step._close();
|
|
2671
2931
|
this.steps.splice(0, this.steps.length, ...remaining);
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
this.client.sendTransaction(this.payload(uuid4(), FLUSH_TRANSACTION_OP, status, startTimestamp, nowISO(), chunk));
|
|
2678
|
-
}
|
|
2932
|
+
for (let i = 0; i < batch.length; i += MAX_STEPS) {
|
|
2933
|
+
const chunk = batch.slice(i, i + MAX_STEPS);
|
|
2934
|
+
const startTimestamp = chunk.reduce((min, step) => step.startTimestamp < min ? step.startTimestamp : min, chunk[0].startTimestamp);
|
|
2935
|
+
const status = chunk.some((step) => step.status === "error") ? "error" : "ok";
|
|
2936
|
+
this.client.sendTransaction(this.payload(uuid4(), FLUSH_TRANSACTION_OP, status, startTimestamp, nowISO(), chunk));
|
|
2679
2937
|
}
|
|
2680
2938
|
return batch.length;
|
|
2681
2939
|
}
|
|
@@ -2692,14 +2950,15 @@ var Transaction = class {
|
|
|
2692
2950
|
start_timestamp: startTimestamp,
|
|
2693
2951
|
timestamp,
|
|
2694
2952
|
steps: steps.map((step) => step.toJSON()),
|
|
2695
|
-
|
|
2953
|
+
// sem tags próprias: o client mescla as do processo
|
|
2954
|
+
tags: void 0,
|
|
2696
2955
|
run_id: this.runId
|
|
2697
2956
|
};
|
|
2698
2957
|
}
|
|
2699
2958
|
finish() {
|
|
2700
2959
|
if (this.finished) return;
|
|
2701
2960
|
this.finished = true;
|
|
2702
|
-
for (const step of this.steps) step.
|
|
2961
|
+
for (const step of this.steps) step._close();
|
|
2703
2962
|
const endTimestamp = nowISO();
|
|
2704
2963
|
this.emit(
|
|
2705
2964
|
"transaction",
|
|
@@ -2727,7 +2986,7 @@ var DEFAULT_RETRY_DELAY_MS = 5e3;
|
|
|
2727
2986
|
async function executeRun(input) {
|
|
2728
2987
|
const { run, handler, api, client, logger, tracker } = input;
|
|
2729
2988
|
const options = handler.options;
|
|
2730
|
-
const tx = new Transaction(client, "run", "hobots.run", run.id);
|
|
2989
|
+
const tx = new Transaction(client, "run", "hobots.run", run.id, { echo: !client });
|
|
2731
2990
|
const reporter = new RunReporter(
|
|
2732
2991
|
api,
|
|
2733
2992
|
run.id,
|
|
@@ -2761,6 +3020,7 @@ async function executeRun(input) {
|
|
|
2761
3020
|
await reporter.finalizeItems();
|
|
2762
3021
|
tx.setStatus("error");
|
|
2763
3022
|
tx.finish();
|
|
3023
|
+
captureRunFailure(client, error, reporter);
|
|
2764
3024
|
reporter.reportFailure(toRunError(error));
|
|
2765
3025
|
logger.debug(`run ${run.id} falhou: ${String(error)}`);
|
|
2766
3026
|
}
|
|
@@ -2770,6 +3030,11 @@ async function executeRun(input) {
|
|
|
2770
3030
|
tracker.remove(reporter);
|
|
2771
3031
|
}
|
|
2772
3032
|
}
|
|
3033
|
+
function captureRunFailure(client, error, reporter) {
|
|
3034
|
+
if (!client?.options.captureRunErrors) return;
|
|
3035
|
+
if (reporter.isDead || isCancellation(error) || wasCaptured(error)) return;
|
|
3036
|
+
client.captureException(error, { type: "error", tags: { mechanism: "run_handler", run_id: reporter.runId } });
|
|
3037
|
+
}
|
|
2773
3038
|
function warnIncompleteProgress(reporter) {
|
|
2774
3039
|
const snapshot = reporter.context.progress.snapshot;
|
|
2775
3040
|
if (!snapshot || snapshot.current >= snapshot.total) return;
|
|
@@ -2961,6 +3226,96 @@ var TaskAgent = class {
|
|
|
2961
3226
|
}
|
|
2962
3227
|
};
|
|
2963
3228
|
|
|
3229
|
+
// src/tasks/single-run.ts
|
|
3230
|
+
var SingleRunRunner = class {
|
|
3231
|
+
constructor(handler, deps) {
|
|
3232
|
+
this.handler = handler;
|
|
3233
|
+
this.deps = deps;
|
|
3234
|
+
this.done = new Promise((resolve2) => {
|
|
3235
|
+
this.resolveDone = resolve2;
|
|
3236
|
+
});
|
|
3237
|
+
}
|
|
3238
|
+
handler;
|
|
3239
|
+
deps;
|
|
3240
|
+
abort = new AbortController();
|
|
3241
|
+
/** Resolve quando a run terminou (ou não pôde começar, ou o runner foi parado antes). */
|
|
3242
|
+
done;
|
|
3243
|
+
resolveDone;
|
|
3244
|
+
async run() {
|
|
3245
|
+
try {
|
|
3246
|
+
const run = await this.openRun();
|
|
3247
|
+
if (!run) return;
|
|
3248
|
+
this.deps.logger.debug(this.opened(run));
|
|
3249
|
+
await executeRun({
|
|
3250
|
+
run,
|
|
3251
|
+
handler: this.handler,
|
|
3252
|
+
api: this.deps.api,
|
|
3253
|
+
client: this.deps.client,
|
|
3254
|
+
logger: this.deps.logger,
|
|
3255
|
+
tracker: this.deps.tracker
|
|
3256
|
+
});
|
|
3257
|
+
} catch (error) {
|
|
3258
|
+
this.deps.logger.error("erro inesperado na execu\xE7\xE3o do agente:", error);
|
|
3259
|
+
} finally {
|
|
3260
|
+
this.resolveDone();
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
/** Desiste de obter a run (close() antes dela existir). Uma run já em execução segue pelo tracker. */
|
|
3264
|
+
stop() {
|
|
3265
|
+
this.abort.abort();
|
|
3266
|
+
}
|
|
3267
|
+
};
|
|
3268
|
+
|
|
3269
|
+
// src/tasks/attached.ts
|
|
3270
|
+
var START_RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
|
|
3271
|
+
function isTransient(error) {
|
|
3272
|
+
if (!(error instanceof TasksApiError)) return true;
|
|
3273
|
+
return error.status >= 500 || error.status === 429;
|
|
3274
|
+
}
|
|
3275
|
+
function describeRejection(runId, error) {
|
|
3276
|
+
if (error instanceof TasksApiError && error.status === 404) {
|
|
3277
|
+
return `run ${runId} n\xE3o existe na plataforma (404) \u2014 nada a executar`;
|
|
3278
|
+
}
|
|
3279
|
+
if (error instanceof TasksApiError && error.status === 409) {
|
|
3280
|
+
return `run ${runId} n\xE3o pode ser iniciada (409: cancelada, j\xE1 iniciada ou de outra inst\xE2ncia) \u2014 nada a executar`;
|
|
3281
|
+
}
|
|
3282
|
+
return `n\xE3o foi poss\xEDvel iniciar a run ${runId} (${String(error)}) \u2014 nada a executar`;
|
|
3283
|
+
}
|
|
3284
|
+
var AttachedRunner = class extends SingleRunRunner {
|
|
3285
|
+
constructor(runId, handler, deps) {
|
|
3286
|
+
super(handler, deps);
|
|
3287
|
+
this.runId = runId;
|
|
3288
|
+
}
|
|
3289
|
+
runId;
|
|
3290
|
+
opened(run) {
|
|
3291
|
+
return `run ${run.id} iniciada (HOBOTS_RUN_ID)`;
|
|
3292
|
+
}
|
|
3293
|
+
async openRun() {
|
|
3294
|
+
for (let attempt = 0; ; attempt++) {
|
|
3295
|
+
if (this.abort.signal.aborted) return void 0;
|
|
3296
|
+
try {
|
|
3297
|
+
return await this.deps.api.startRun(this.runId, { instance: this.deps.instanceInfo() }, this.abort.signal);
|
|
3298
|
+
} catch (error) {
|
|
3299
|
+
if (this.abort.signal.aborted) return void 0;
|
|
3300
|
+
if (!isTransient(error)) {
|
|
3301
|
+
this.deps.logger.error(describeRejection(this.runId, error));
|
|
3302
|
+
return void 0;
|
|
3303
|
+
}
|
|
3304
|
+
const delay = START_RETRY_DELAYS_MS[attempt];
|
|
3305
|
+
if (delay === void 0) {
|
|
3306
|
+
this.deps.logger.error(
|
|
3307
|
+
`n\xE3o foi poss\xEDvel iniciar a run ${this.runId} ap\xF3s ${attempt + 1} tentativas (${String(error)}) \u2014 desistindo`
|
|
3308
|
+
);
|
|
3309
|
+
return void 0;
|
|
3310
|
+
}
|
|
3311
|
+
const wait = jitter(delay);
|
|
3312
|
+
this.deps.logger.warn(`in\xEDcio da run ${this.runId} falhou (${String(error)}), pr\xF3xima tentativa em ${wait}ms`);
|
|
3313
|
+
await abortableSleep(wait, this.abort.signal);
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
};
|
|
3318
|
+
|
|
2964
3319
|
// src/tasks/item-columns.ts
|
|
2965
3320
|
var KEY_RE = /^[a-zA-Z0-9_.-]{1,100}$/;
|
|
2966
3321
|
var MAX_COLUMNS = WIRE_LIMITS.MAX_ITEM_COLUMNS;
|
|
@@ -2998,46 +3353,16 @@ function normalizeItemColumns(input, logger) {
|
|
|
2998
3353
|
}
|
|
2999
3354
|
|
|
3000
3355
|
// src/tasks/local.ts
|
|
3001
|
-
var LocalRunner = class {
|
|
3356
|
+
var LocalRunner = class extends SingleRunRunner {
|
|
3357
|
+
backoff;
|
|
3002
3358
|
constructor(handler, deps) {
|
|
3003
|
-
|
|
3004
|
-
this.deps = deps;
|
|
3359
|
+
super(handler, deps);
|
|
3005
3360
|
this.backoff = new Backoff(deps.logger, "cria\xE7\xE3o da run");
|
|
3006
|
-
this.done = new Promise((resolve2) => {
|
|
3007
|
-
this.resolveDone = resolve2;
|
|
3008
|
-
});
|
|
3009
|
-
}
|
|
3010
|
-
handler;
|
|
3011
|
-
deps;
|
|
3012
|
-
abort = new AbortController();
|
|
3013
|
-
backoff;
|
|
3014
|
-
/** Resolve quando a run terminou (ou o runner foi parado antes de abri-la). */
|
|
3015
|
-
done;
|
|
3016
|
-
resolveDone;
|
|
3017
|
-
async run() {
|
|
3018
|
-
try {
|
|
3019
|
-
const run = await this.createRun();
|
|
3020
|
-
if (!run) return;
|
|
3021
|
-
this.deps.logger.debug(`run ${run.id} aberta (modo start)`);
|
|
3022
|
-
await executeRun({
|
|
3023
|
-
run,
|
|
3024
|
-
handler: this.handler,
|
|
3025
|
-
api: this.deps.api,
|
|
3026
|
-
client: this.deps.client,
|
|
3027
|
-
logger: this.deps.logger,
|
|
3028
|
-
tracker: this.deps.tracker
|
|
3029
|
-
});
|
|
3030
|
-
} catch (error) {
|
|
3031
|
-
this.deps.logger.error("erro inesperado na execu\xE7\xE3o do agente:", error);
|
|
3032
|
-
} finally {
|
|
3033
|
-
this.resolveDone();
|
|
3034
|
-
}
|
|
3035
3361
|
}
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
this.abort.abort();
|
|
3362
|
+
opened(run) {
|
|
3363
|
+
return `run ${run.id} aberta (modo start)`;
|
|
3039
3364
|
}
|
|
3040
|
-
async
|
|
3365
|
+
async openRun() {
|
|
3041
3366
|
for (; ; ) {
|
|
3042
3367
|
if (this.abort.signal.aborted) return void 0;
|
|
3043
3368
|
try {
|
|
@@ -3050,6 +3375,129 @@ var LocalRunner = class {
|
|
|
3050
3375
|
}
|
|
3051
3376
|
};
|
|
3052
3377
|
|
|
3378
|
+
// src/tasks/local-transport.ts
|
|
3379
|
+
var LOCAL_RUN_ID = "local";
|
|
3380
|
+
var ITEM_LINE_PREFIX = "[hobots:item]";
|
|
3381
|
+
var SUMMARY_LINE_PREFIX = "[hobots:summary]";
|
|
3382
|
+
var ATTACHMENTS_DISCARDED_WARNING = "imagens e anexos n\xE3o s\xE3o enviados no modo local (descartados)";
|
|
3383
|
+
function unavailable(name) {
|
|
3384
|
+
return new Error(`arquivo "${name}" enviado pela Central n\xE3o est\xE1 dispon\xEDvel no modo local`);
|
|
3385
|
+
}
|
|
3386
|
+
function cell(value) {
|
|
3387
|
+
return (value ?? "").replace(/\r\n|[\t\r\n]/g, " ");
|
|
3388
|
+
}
|
|
3389
|
+
var LocalRunTransport = class {
|
|
3390
|
+
constructor(params = {}, out = (line) => void process.stdout.write(`${line}
|
|
3391
|
+
`)) {
|
|
3392
|
+
this.params = params;
|
|
3393
|
+
this.out = out;
|
|
3394
|
+
}
|
|
3395
|
+
params;
|
|
3396
|
+
out;
|
|
3397
|
+
counts = { succeeded: 0, occurrence: 0, failed: 0 };
|
|
3398
|
+
warnedAttachments = false;
|
|
3399
|
+
/** Status terminal que o pipeline reportou — `undefined` enquanto a run corre (ou se ela nem reportou). */
|
|
3400
|
+
terminalStatus;
|
|
3401
|
+
async updateRun(_runId, update) {
|
|
3402
|
+
if (update.status === "running") return;
|
|
3403
|
+
this.terminalStatus = update.status;
|
|
3404
|
+
if (update.status === "failed" && update.error && update.error.type !== "RunCanceled") {
|
|
3405
|
+
printRunLog("error", `execu\xE7\xE3o falhou \u2014 ${update.error.type ?? "Error"}: ${update.error.message}`);
|
|
3406
|
+
if (update.error.stack) console.error(update.error.stack);
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
async updateRunOnce() {
|
|
3410
|
+
}
|
|
3411
|
+
async createItems(_runId, request) {
|
|
3412
|
+
for (const item of request.items) {
|
|
3413
|
+
this.counts[item.status]++;
|
|
3414
|
+
this.out(`${ITEM_LINE_PREFIX} ${item.status} ${cell(item.id)} ${cell(item.message)}`);
|
|
3415
|
+
}
|
|
3416
|
+
return { accepted: request.items.length, duplicates: 0 };
|
|
3417
|
+
}
|
|
3418
|
+
/** Totais por status — chame uma vez, no fim da run, antes da linha de conclusão. */
|
|
3419
|
+
printSummary() {
|
|
3420
|
+
const { succeeded, occurrence, failed } = this.counts;
|
|
3421
|
+
this.out(
|
|
3422
|
+
`${SUMMARY_LINE_PREFIX} total=${succeeded + occurrence + failed} ok=${succeeded} avisos=${occurrence} falhas=${failed} pulados=0`
|
|
3423
|
+
);
|
|
3424
|
+
}
|
|
3425
|
+
async createAttachment(_runId, request) {
|
|
3426
|
+
if (!this.warnedAttachments) {
|
|
3427
|
+
this.warnedAttachments = true;
|
|
3428
|
+
printRunLog("warning", ATTACHMENTS_DISCARDED_WARNING);
|
|
3429
|
+
}
|
|
3430
|
+
return { id: request.id, upload_url: "", headers: {}, expires_in: 0 };
|
|
3431
|
+
}
|
|
3432
|
+
async uploadAttachment() {
|
|
3433
|
+
}
|
|
3434
|
+
async confirmAttachment() {
|
|
3435
|
+
}
|
|
3436
|
+
/** Nome do arquivo pelo id (nos params); sem ele, o próprio id. */
|
|
3437
|
+
fileName(fileId) {
|
|
3438
|
+
return collectFileRefs(this.params).find((ref) => ref.id === fileId)?.name ?? fileId;
|
|
3439
|
+
}
|
|
3440
|
+
async requestFileDownload(_runId, fileId) {
|
|
3441
|
+
throw unavailable(this.fileName(fileId));
|
|
3442
|
+
}
|
|
3443
|
+
async fetchFile(url) {
|
|
3444
|
+
throw unavailable(url);
|
|
3445
|
+
}
|
|
3446
|
+
async confirmFileDownload(_runId, fileId) {
|
|
3447
|
+
throw unavailable(this.fileName(fileId));
|
|
3448
|
+
}
|
|
3449
|
+
};
|
|
3450
|
+
|
|
3451
|
+
// src/tasks/local-run.ts
|
|
3452
|
+
var LOCAL_RUN_EXIT_CODES = { succeeded: 0, failed: 1, canceled: 130 };
|
|
3453
|
+
var LOCAL_RUN_CANCEL_GRACE_MS = 4e3;
|
|
3454
|
+
var LOCAL_RUN_BANNER = "modo local \u2014 execu\xE7\xE3o sem a Central; params de HOBOTS_RUN_PARAMS";
|
|
3455
|
+
var LocalRunRunner = class extends SingleRunRunner {
|
|
3456
|
+
constructor(params, handler, deps) {
|
|
3457
|
+
super(handler, deps);
|
|
3458
|
+
this.params = params;
|
|
3459
|
+
}
|
|
3460
|
+
params;
|
|
3461
|
+
canceled = false;
|
|
3462
|
+
startedAt = Date.now();
|
|
3463
|
+
finished = false;
|
|
3464
|
+
run() {
|
|
3465
|
+
this.startedAt = Date.now();
|
|
3466
|
+
return super.run();
|
|
3467
|
+
}
|
|
3468
|
+
opened() {
|
|
3469
|
+
return "run local iniciada (HOBOTS_LOCAL_RUN)";
|
|
3470
|
+
}
|
|
3471
|
+
async openRun() {
|
|
3472
|
+
if (this.abort.signal.aborted) return void 0;
|
|
3473
|
+
return { id: LOCAL_RUN_ID, params: this.params, form: null, source: "local", created_at: nowISO() };
|
|
3474
|
+
}
|
|
3475
|
+
/** SIGINT/SIGTERM: dispara o `ctx.signal` da run em voo — o handler para no próximo ponto de parada. */
|
|
3476
|
+
cancel() {
|
|
3477
|
+
this.canceled = true;
|
|
3478
|
+
this.deps.tracker.singleActive()?.abort();
|
|
3479
|
+
}
|
|
3480
|
+
/** Desfecho da run: cancelada pelo sinal, senão o status terminal reportado (sem reporte = falha). */
|
|
3481
|
+
get outcome() {
|
|
3482
|
+
if (this.canceled) return "canceled";
|
|
3483
|
+
return this.deps.api.terminalStatus ?? "failed";
|
|
3484
|
+
}
|
|
3485
|
+
/**
|
|
3486
|
+
* Fecha o relato no terminal — `[hobots:summary]` e a linha de conclusão — e devolve o
|
|
3487
|
+
* desfecho. Idempotente: o runtime chama ao fim da run ou quando a graça do cancelamento
|
|
3488
|
+
* esgota com o handler ainda rodando.
|
|
3489
|
+
*/
|
|
3490
|
+
finish() {
|
|
3491
|
+
const outcome = this.outcome;
|
|
3492
|
+
if (this.finished) return outcome;
|
|
3493
|
+
this.finished = true;
|
|
3494
|
+
this.deps.api.printSummary();
|
|
3495
|
+
const seconds = Math.round((Date.now() - this.startedAt) / 1e3);
|
|
3496
|
+
printRunLog("info", `execu\xE7\xE3o local conclu\xEDda (${outcome}) em ${seconds}s`);
|
|
3497
|
+
return outcome;
|
|
3498
|
+
}
|
|
3499
|
+
};
|
|
3500
|
+
|
|
3053
3501
|
// src/tasks/registry.ts
|
|
3054
3502
|
var RunTracker = class {
|
|
3055
3503
|
constructor(api) {
|
|
@@ -3165,19 +3613,10 @@ function hostname() {
|
|
|
3165
3613
|
if (!isNode()) return void 0;
|
|
3166
3614
|
return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
|
|
3167
3615
|
}
|
|
3168
|
-
function
|
|
3169
|
-
|
|
3170
|
-
const logger = makeLogger(options.debug ?? false);
|
|
3171
|
-
const environment = resolveEnvironmentOption(options.environment);
|
|
3172
|
-
const client = new Client(
|
|
3173
|
-
{ environment, release: options.release, tags: options.tags, debug: options.debug },
|
|
3174
|
-
config
|
|
3175
|
-
);
|
|
3176
|
-
const api = new TasksApi({ baseUrl: config.runsBaseUrl, key: config.key, logger });
|
|
3177
|
-
const rt = {
|
|
3616
|
+
function baseRuntime(options, handler, logger, api) {
|
|
3617
|
+
return {
|
|
3178
3618
|
options: { ...options, clientSecret: void 0 },
|
|
3179
|
-
|
|
3180
|
-
client,
|
|
3619
|
+
client: void 0,
|
|
3181
3620
|
logger,
|
|
3182
3621
|
api,
|
|
3183
3622
|
handler,
|
|
@@ -3186,18 +3625,55 @@ function createRuntime(options, handler) {
|
|
|
3186
3625
|
heartbeat: void 0,
|
|
3187
3626
|
agent: void 0,
|
|
3188
3627
|
local: void 0,
|
|
3628
|
+
attached: void 0,
|
|
3629
|
+
localRun: void 0,
|
|
3630
|
+
runId: void 0,
|
|
3189
3631
|
instanceId: resolveInstanceId(options.instanceId),
|
|
3190
|
-
environment,
|
|
3632
|
+
environment: void 0,
|
|
3191
3633
|
pollIntervalMs: void 0,
|
|
3192
3634
|
booted: false,
|
|
3193
3635
|
bootScheduled: false,
|
|
3194
3636
|
bootPromise: void 0,
|
|
3195
|
-
uninstallGlobalHandlers:
|
|
3637
|
+
uninstallGlobalHandlers: void 0,
|
|
3196
3638
|
uninstallShutdown: void 0
|
|
3197
3639
|
};
|
|
3640
|
+
}
|
|
3641
|
+
function createRuntime(options, handler, logger) {
|
|
3642
|
+
const config = resolveConfig({ clientSecret: options.clientSecret });
|
|
3643
|
+
const environment = resolveEnvironmentOption(options.environment);
|
|
3644
|
+
const client = new Client(
|
|
3645
|
+
{
|
|
3646
|
+
environment,
|
|
3647
|
+
release: options.release,
|
|
3648
|
+
tags: options.tags,
|
|
3649
|
+
debug: options.debug,
|
|
3650
|
+
captureRunErrors: options.captureUnhandled !== false
|
|
3651
|
+
},
|
|
3652
|
+
config
|
|
3653
|
+
);
|
|
3654
|
+
const api = new TasksApi({ baseUrl: config.runsBaseUrl, key: config.key, logger });
|
|
3655
|
+
const rt = baseRuntime(options, handler, logger, api);
|
|
3656
|
+
rt.client = client;
|
|
3657
|
+
rt.environment = environment;
|
|
3658
|
+
rt.uninstallGlobalHandlers = options.captureUnhandled !== false ? installGlobalHandlers(client) : void 0;
|
|
3198
3659
|
logger.debug("inicializado", { endpoint: config.ingestEndpoint });
|
|
3199
3660
|
return rt;
|
|
3200
3661
|
}
|
|
3662
|
+
function createLocalRunRuntime(options, handler, logger) {
|
|
3663
|
+
let params;
|
|
3664
|
+
try {
|
|
3665
|
+
params = resolveLocalRunParams();
|
|
3666
|
+
} catch (error) {
|
|
3667
|
+
printRunLog("error", error instanceof Error ? error.message : String(error));
|
|
3668
|
+
process.exit(2);
|
|
3669
|
+
throw error;
|
|
3670
|
+
}
|
|
3671
|
+
const transport = new LocalRunTransport(params);
|
|
3672
|
+
const rt = baseRuntime(options, handler, logger, transport);
|
|
3673
|
+
rt.localRun = new LocalRunRunner(params, handler, { api: transport, client: void 0, logger, tracker: rt.tracker });
|
|
3674
|
+
printRunLog("info", LOCAL_RUN_BANNER);
|
|
3675
|
+
return rt;
|
|
3676
|
+
}
|
|
3201
3677
|
function validateHandler(handler, fn) {
|
|
3202
3678
|
if (typeof handler !== "function") {
|
|
3203
3679
|
throw new Error(`[hobots] ${fn}(): o handler precisa ser uma fun\xE7\xE3o \u2014 ${fn}(async (params) => { ... })`);
|
|
@@ -3240,9 +3716,12 @@ function instanceInfo(rt) {
|
|
|
3240
3716
|
machine: rt.sampler?.sample()
|
|
3241
3717
|
};
|
|
3242
3718
|
}
|
|
3719
|
+
function platformApi(rt) {
|
|
3720
|
+
return rt.api;
|
|
3721
|
+
}
|
|
3243
3722
|
function ensureAgent(rt) {
|
|
3244
3723
|
rt.agent ??= new TaskAgent({
|
|
3245
|
-
api: rt
|
|
3724
|
+
api: platformApi(rt),
|
|
3246
3725
|
client: rt.client,
|
|
3247
3726
|
logger: rt.logger,
|
|
3248
3727
|
handler: rt.handler,
|
|
@@ -3254,7 +3733,7 @@ function ensureAgent(rt) {
|
|
|
3254
3733
|
}
|
|
3255
3734
|
function launchLocal(rt) {
|
|
3256
3735
|
const runner = new LocalRunner(rt.handler, {
|
|
3257
|
-
api: rt
|
|
3736
|
+
api: platformApi(rt),
|
|
3258
3737
|
client: rt.client,
|
|
3259
3738
|
logger: rt.logger,
|
|
3260
3739
|
tracker: rt.tracker,
|
|
@@ -3263,7 +3742,57 @@ function launchLocal(rt) {
|
|
|
3263
3742
|
rt.local = runner;
|
|
3264
3743
|
void runner.run();
|
|
3265
3744
|
}
|
|
3745
|
+
function launchAttached(rt, runId) {
|
|
3746
|
+
const runner = new AttachedRunner(runId, rt.handler, {
|
|
3747
|
+
api: platformApi(rt),
|
|
3748
|
+
client: rt.client,
|
|
3749
|
+
logger: rt.logger,
|
|
3750
|
+
tracker: rt.tracker,
|
|
3751
|
+
instanceInfo: () => instanceInfo(rt)
|
|
3752
|
+
});
|
|
3753
|
+
rt.attached = runner;
|
|
3754
|
+
void runner.run();
|
|
3755
|
+
void runner.done.then(() => {
|
|
3756
|
+
if (runtime === rt) void close();
|
|
3757
|
+
});
|
|
3758
|
+
}
|
|
3759
|
+
function launchLocalRun(rt, runner) {
|
|
3760
|
+
let finished = false;
|
|
3761
|
+
const finish = () => {
|
|
3762
|
+
if (finished) return;
|
|
3763
|
+
finished = true;
|
|
3764
|
+
rt.uninstallShutdown?.();
|
|
3765
|
+
rt.uninstallShutdown = void 0;
|
|
3766
|
+
const code = LOCAL_RUN_EXIT_CODES[runner.finish()];
|
|
3767
|
+
if (runtime === rt) void close(0);
|
|
3768
|
+
process.stdout.write("", () => process.exit(code));
|
|
3769
|
+
};
|
|
3770
|
+
if (rt.options.installExitHandlers !== false) {
|
|
3771
|
+
let signaled = false;
|
|
3772
|
+
const onSignal = (signal) => {
|
|
3773
|
+
if (externalListenerCount(signal) > 0 || signaled) return;
|
|
3774
|
+
signaled = true;
|
|
3775
|
+
runner.cancel();
|
|
3776
|
+
void Promise.race([runner.done, sleep(LOCAL_RUN_CANCEL_GRACE_MS)]).then(finish);
|
|
3777
|
+
};
|
|
3778
|
+
const onInt = markInternal(() => onSignal("SIGINT"));
|
|
3779
|
+
const onTerm = markInternal(() => onSignal("SIGTERM"));
|
|
3780
|
+
process.on("SIGINT", onInt);
|
|
3781
|
+
process.on("SIGTERM", onTerm);
|
|
3782
|
+
rt.uninstallShutdown = () => {
|
|
3783
|
+
process.off("SIGINT", onInt);
|
|
3784
|
+
process.off("SIGTERM", onTerm);
|
|
3785
|
+
};
|
|
3786
|
+
}
|
|
3787
|
+
void runner.run();
|
|
3788
|
+
void runner.done.then(finish);
|
|
3789
|
+
}
|
|
3266
3790
|
async function boot(rt) {
|
|
3791
|
+
if (rt.localRun) {
|
|
3792
|
+
rt.booted = true;
|
|
3793
|
+
launchLocalRun(rt, rt.localRun);
|
|
3794
|
+
return;
|
|
3795
|
+
}
|
|
3267
3796
|
if (!rt.instanceId) {
|
|
3268
3797
|
const id = await fallbackInstanceId();
|
|
3269
3798
|
if (runtime !== rt) return;
|
|
@@ -3282,8 +3811,9 @@ async function boot(rt) {
|
|
|
3282
3811
|
if (rt.options.installExitHandlers !== false) {
|
|
3283
3812
|
rt.uninstallShutdown = installShutdownHandlers(rt.tracker);
|
|
3284
3813
|
}
|
|
3285
|
-
if (rt.handler.mode === "
|
|
3286
|
-
else
|
|
3814
|
+
if (rt.handler.mode === "local") launchLocal(rt);
|
|
3815
|
+
else if (rt.runId) launchAttached(rt, rt.runId);
|
|
3816
|
+
else ensureAgent(rt).start();
|
|
3287
3817
|
}
|
|
3288
3818
|
function scheduleBoot(rt) {
|
|
3289
3819
|
if (rt.bootScheduled) return;
|
|
@@ -3299,8 +3829,12 @@ function startAgent(fn, mode, handler, options) {
|
|
|
3299
3829
|
}
|
|
3300
3830
|
const logger = makeLogger(options.debug ?? false);
|
|
3301
3831
|
const agentHandler = { mode, handler, options: resolveRunOptions(options, mode, logger) };
|
|
3302
|
-
|
|
3303
|
-
|
|
3832
|
+
loadDotenv();
|
|
3833
|
+
const rt = isLocalRun() ? createLocalRunRuntime(options, agentHandler, logger) : createRuntime(options, agentHandler, logger);
|
|
3834
|
+
if (!rt.localRun && mode === "remote") {
|
|
3835
|
+
if (options.pollIntervalMs !== void 0) rt.pollIntervalMs = options.pollIntervalMs;
|
|
3836
|
+
rt.runId = resolveRunId(options.runId);
|
|
3837
|
+
}
|
|
3304
3838
|
runtime = rt;
|
|
3305
3839
|
scheduleBoot(rt);
|
|
3306
3840
|
}
|
|
@@ -3323,10 +3857,10 @@ function forwardAttachment(rt, attachment, message, type) {
|
|
|
3323
3857
|
const reporter = currentReporter();
|
|
3324
3858
|
if (!reporter) {
|
|
3325
3859
|
rt.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o");
|
|
3326
|
-
return;
|
|
3860
|
+
return void 0;
|
|
3327
3861
|
}
|
|
3328
3862
|
const withMeta = typeof attachment === "object" && !isImageAttachment(attachment) && "image" in attachment ? image(attachment.image, { caption: attachment.caption, contentType: attachment.contentType }) : attachment;
|
|
3329
|
-
reporter.log(toRunLogLevel(type), message, withMeta);
|
|
3863
|
+
return reporter.log(toRunLogLevel(type), message, withMeta);
|
|
3330
3864
|
}
|
|
3331
3865
|
function captureException(error, hint) {
|
|
3332
3866
|
const rt = runtime;
|
|
@@ -3337,11 +3871,19 @@ function captureException(error, hint) {
|
|
|
3337
3871
|
}
|
|
3338
3872
|
return void 0;
|
|
3339
3873
|
}
|
|
3874
|
+
let attachmentId;
|
|
3340
3875
|
if (hint?.attachment) {
|
|
3341
3876
|
const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
3342
|
-
forwardAttachment(rt, hint.attachment, message, hint.type ?? "error");
|
|
3877
|
+
attachmentId = forwardAttachment(rt, hint.attachment, message, hint.type ?? "error");
|
|
3343
3878
|
}
|
|
3344
|
-
|
|
3879
|
+
if (!rt.client) {
|
|
3880
|
+
rt.logger.debug("captureException ignorado no modo local (sem Central)");
|
|
3881
|
+
return void 0;
|
|
3882
|
+
}
|
|
3883
|
+
const runId = currentReporter()?.runId;
|
|
3884
|
+
const withRun = runId ? { ...hint, tags: { run_id: runId, ...hint?.tags } } : hint;
|
|
3885
|
+
const contexts = runId && attachmentId ? { run: { id: runId, attachments: [attachmentId] } } : void 0;
|
|
3886
|
+
return rt.client.captureException(error, withRun, contexts);
|
|
3345
3887
|
}
|
|
3346
3888
|
async function close(timeoutMs = 3e4) {
|
|
3347
3889
|
const rt = runtime;
|
|
@@ -3350,6 +3892,8 @@ async function close(timeoutMs = 3e4) {
|
|
|
3350
3892
|
await rt.bootPromise;
|
|
3351
3893
|
if (rt.agent) await rt.agent.stop(0);
|
|
3352
3894
|
rt.local?.stop();
|
|
3895
|
+
rt.attached?.stop();
|
|
3896
|
+
rt.localRun?.stop();
|
|
3353
3897
|
const idle = await rt.tracker.waitIdle(timeoutMs);
|
|
3354
3898
|
if (!idle) {
|
|
3355
3899
|
rt.logger.warn(`${rt.tracker.activeCount} run(s) ainda em execu\xE7\xE3o ap\xF3s ${timeoutMs}ms \u2014 abandonando a espera`);
|
|
@@ -3358,7 +3902,7 @@ async function close(timeoutMs = 3e4) {
|
|
|
3358
3902
|
rt.heartbeat?.stop();
|
|
3359
3903
|
rt.uninstallShutdown?.();
|
|
3360
3904
|
rt.uninstallGlobalHandlers?.();
|
|
3361
|
-
const flushed = await rt.client.close(timeoutMs);
|
|
3905
|
+
const flushed = rt.client ? await rt.client.close(timeoutMs) : true;
|
|
3362
3906
|
return idle && flushed;
|
|
3363
3907
|
}
|
|
3364
3908
|
|
|
@@ -3387,11 +3931,13 @@ var noop = () => void 0;
|
|
|
3387
3931
|
var fallbackEachSink = {
|
|
3388
3932
|
report: noop,
|
|
3389
3933
|
startStep: () => void 0,
|
|
3934
|
+
stepScope: (_step, fn) => fn(),
|
|
3390
3935
|
declareTotal: noop,
|
|
3391
3936
|
advance: noop,
|
|
3392
3937
|
checkCanceled: noop,
|
|
3393
3938
|
isAborted: () => false,
|
|
3394
|
-
logError: (message) => printRunLog("error", message)
|
|
3939
|
+
logError: (message) => printRunLog("error", message),
|
|
3940
|
+
flush: () => Promise.resolve()
|
|
3395
3941
|
};
|
|
3396
3942
|
var fallbackItems = {
|
|
3397
3943
|
add: noop,
|
|
@@ -3410,7 +3956,6 @@ var fallbackFiles = {
|
|
|
3410
3956
|
read: outsideRunFiles
|
|
3411
3957
|
};
|
|
3412
3958
|
var neverAborts = new AbortController().signal;
|
|
3413
|
-
var detachedTransaction;
|
|
3414
3959
|
function resolve(member) {
|
|
3415
3960
|
const reporter = currentReporter();
|
|
3416
3961
|
if (reporter) return reporter.context;
|
|
@@ -3431,8 +3976,12 @@ var hobots = Object.defineProperties({}, {
|
|
|
3431
3976
|
items: getter("items", () => fallbackItems),
|
|
3432
3977
|
progress: getter("progress", () => fallbackProgress),
|
|
3433
3978
|
files: getter("files", () => fallbackFiles),
|
|
3434
|
-
transaction: getter("transaction", () => detachedTransaction ??= new Transaction(void 0, "hobots.detached")),
|
|
3435
3979
|
signal: getter("signal", () => neverAborts),
|
|
3980
|
+
step: method((name, fn, options) => {
|
|
3981
|
+
const ctx = resolve("step");
|
|
3982
|
+
if (ctx) return ctx.step(name, fn, options);
|
|
3983
|
+
return new Transaction(void 0, "hobots.detached").step(name, fn, options);
|
|
3984
|
+
}),
|
|
3436
3985
|
throwIfCanceled: method(() => resolve("throwIfCanceled")?.throwIfCanceled()),
|
|
3437
3986
|
sleep: method((ms) => resolve("sleep")?.sleep(ms) ?? sleep(ms)),
|
|
3438
3987
|
flush: method((timeoutMs) => resolve("flush")?.flush(timeoutMs) ?? Promise.resolve()),
|
|
@@ -3467,5 +4016,6 @@ var image2 = hobots.image;
|
|
|
3467
4016
|
var isFileRef2 = hobots.isFileRef;
|
|
3468
4017
|
|
|
3469
4018
|
// src/index.ts
|
|
4019
|
+
loadDotenv();
|
|
3470
4020
|
var src_default = hobots;
|
|
3471
4021
|
//# sourceMappingURL=index.cjs.map
|