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.js
CHANGED
|
@@ -2,62 +2,9 @@ import {
|
|
|
2
2
|
WIRE_LIMITS
|
|
3
3
|
} from "./chunk-HKCDMUA3.js";
|
|
4
4
|
|
|
5
|
-
// src/stacktrace.ts
|
|
6
|
-
var MAX_FRAMES = 50;
|
|
7
|
-
var MAX_FILENAME_CHARS = 1024;
|
|
8
|
-
var FRAME_RE = /^\s*at\s+(?:async\s+)?(?:(.+?)\s+\()?(.+?)\)?$/;
|
|
9
|
-
var LOCATION_RE = /^(.*?):(\d+):(\d+)$/;
|
|
10
|
-
function isInApp(filename) {
|
|
11
|
-
if (!filename) return false;
|
|
12
|
-
if (filename.startsWith("node:")) return false;
|
|
13
|
-
if (filename.startsWith("internal/")) return false;
|
|
14
|
-
if (filename.includes("node_modules")) return false;
|
|
15
|
-
if (filename === "<anonymous>") return false;
|
|
16
|
-
return true;
|
|
17
|
-
}
|
|
18
|
-
function parseStacktrace(stack) {
|
|
19
|
-
if (!stack) return [];
|
|
20
|
-
const frames = [];
|
|
21
|
-
for (const line of stack.split("\n")) {
|
|
22
|
-
if (frames.length >= MAX_FRAMES) break;
|
|
23
|
-
const match = FRAME_RE.exec(line);
|
|
24
|
-
if (!match || !line.trimStart().startsWith("at ")) continue;
|
|
25
|
-
const [, fn, location] = match;
|
|
26
|
-
const loc = LOCATION_RE.exec(location ?? "");
|
|
27
|
-
const filename = loc ? loc[1] : location;
|
|
28
|
-
frames.push({
|
|
29
|
-
function: fn || "<anonymous>",
|
|
30
|
-
filename: filename ? filename.slice(0, MAX_FILENAME_CHARS) : void 0,
|
|
31
|
-
lineno: loc?.[2] ? Number(loc[2]) : void 0,
|
|
32
|
-
colno: loc?.[3] ? Number(loc[3]) : void 0,
|
|
33
|
-
in_app: isInApp(filename)
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
return frames;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// src/scrub.ts
|
|
40
|
-
var SECRET_PATTERNS = [
|
|
41
|
-
// client secret da Hobots
|
|
42
|
-
[/hb_[a-f0-9]{16,}/gi, "[REDACTED]"],
|
|
43
|
-
// tokens bearer em headers/mensagens
|
|
44
|
-
[/\b(authorization:?\s*)?bearer\s+[a-z0-9._~+/=-]{8,}/gi, "Bearer [REDACTED]"],
|
|
45
|
-
// chaves no formato sk-… (OpenAI e afins)
|
|
46
|
-
[/\bsk-[a-z0-9_-]{16,}\b/gi, "[REDACTED]"],
|
|
47
|
-
// credenciais embutidas em URL (https://user:pass@host)
|
|
48
|
-
[/\/\/[^/\s:@]+:[^/\s@]+@/g, "//[REDACTED]@"]
|
|
49
|
-
];
|
|
50
|
-
function scrubSecrets(text) {
|
|
51
|
-
let out = text;
|
|
52
|
-
for (const [pattern, replacement] of SECRET_PATTERNS) {
|
|
53
|
-
out = out.replace(pattern, replacement);
|
|
54
|
-
}
|
|
55
|
-
return out;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
5
|
// src/utils.ts
|
|
59
6
|
var SDK_NAME = "hobots";
|
|
60
|
-
var SDK_VERSION = true ? "
|
|
7
|
+
var SDK_VERSION = true ? "5.0.0" : "0.0.0-dev";
|
|
61
8
|
function isNode() {
|
|
62
9
|
return typeof process !== "undefined" && typeof process.versions?.node === "string";
|
|
63
10
|
}
|
|
@@ -110,8 +57,13 @@ function abortableSleep(ms, signal) {
|
|
|
110
57
|
function jitter(baseMs) {
|
|
111
58
|
return Math.floor(baseMs / 2 + Math.random() * (baseMs / 2));
|
|
112
59
|
}
|
|
60
|
+
var TRUNCATION_SUFFIX = "\u2026 [truncado]";
|
|
113
61
|
function truncate(value, max) {
|
|
114
|
-
return value.length > max ? `${value.slice(0, max)}
|
|
62
|
+
return value.length > max ? `${value.slice(0, max)}${TRUNCATION_SUFFIX}` : value;
|
|
63
|
+
}
|
|
64
|
+
function clip(value, max) {
|
|
65
|
+
if (value.length <= max) return value;
|
|
66
|
+
return `${value.slice(0, Math.max(0, max - TRUNCATION_SUFFIX.length))}${TRUNCATION_SUFFIX}`;
|
|
115
67
|
}
|
|
116
68
|
function safeString(value) {
|
|
117
69
|
if (typeof value === "string") return value;
|
|
@@ -205,6 +157,124 @@ function colorize(text, color) {
|
|
|
205
157
|
return `${ANSI[color]}${text}${ANSI.reset}`;
|
|
206
158
|
}
|
|
207
159
|
|
|
160
|
+
// src/env.ts
|
|
161
|
+
var INSTANCE_ID_ENV = "HOBOTS_INSTANCE_ID";
|
|
162
|
+
var ENVIRONMENT_ENV = "HOBOTS_ENVIRONMENT";
|
|
163
|
+
var RUN_ID_ENV = "HOBOTS_RUN_ID";
|
|
164
|
+
var LOCAL_RUN_ENV = "HOBOTS_LOCAL_RUN";
|
|
165
|
+
var RUN_PARAMS_ENV = "HOBOTS_RUN_PARAMS";
|
|
166
|
+
var dotenvLoaded = false;
|
|
167
|
+
function loadDotenv() {
|
|
168
|
+
if (dotenvLoaded) return;
|
|
169
|
+
dotenvLoaded = true;
|
|
170
|
+
if (!isNode() || typeof process.loadEnvFile !== "function") return;
|
|
171
|
+
try {
|
|
172
|
+
process.loadEnvFile(".env");
|
|
173
|
+
} catch (err) {
|
|
174
|
+
const code = err?.code;
|
|
175
|
+
if (code === "ENOENT") return;
|
|
176
|
+
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`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function resolveInstanceId(option) {
|
|
180
|
+
const fromOption = option?.trim();
|
|
181
|
+
if (fromOption) return fromOption;
|
|
182
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
183
|
+
return process.env[INSTANCE_ID_ENV]?.trim() || void 0;
|
|
184
|
+
}
|
|
185
|
+
function resolveRunId(option) {
|
|
186
|
+
const fromOption = option?.trim();
|
|
187
|
+
if (fromOption) return fromOption;
|
|
188
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
189
|
+
return process.env[RUN_ID_ENV]?.trim() || void 0;
|
|
190
|
+
}
|
|
191
|
+
function isLocalRun() {
|
|
192
|
+
if (typeof process === "undefined" || !process.env) return false;
|
|
193
|
+
return process.env[LOCAL_RUN_ENV]?.trim() === "1";
|
|
194
|
+
}
|
|
195
|
+
function resolveLocalRunParams() {
|
|
196
|
+
const raw = process.env[RUN_PARAMS_ENV]?.trim();
|
|
197
|
+
if (!raw) return {};
|
|
198
|
+
let parsed;
|
|
199
|
+
try {
|
|
200
|
+
parsed = JSON.parse(raw);
|
|
201
|
+
} catch {
|
|
202
|
+
throw new Error(`${RUN_PARAMS_ENV} inv\xE1lido: n\xE3o \xE9 JSON v\xE1lido`);
|
|
203
|
+
}
|
|
204
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
205
|
+
throw new Error(`${RUN_PARAMS_ENV} inv\xE1lido: esperado um objeto JSON`);
|
|
206
|
+
}
|
|
207
|
+
return parsed;
|
|
208
|
+
}
|
|
209
|
+
function resolveEnvironmentOption(option) {
|
|
210
|
+
const fromOption = option?.trim();
|
|
211
|
+
if (fromOption) return fromOption;
|
|
212
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
213
|
+
return process.env[ENVIRONMENT_ENV]?.trim() || void 0;
|
|
214
|
+
}
|
|
215
|
+
async function fallbackInstanceId() {
|
|
216
|
+
if (!isNode()) return "browser";
|
|
217
|
+
try {
|
|
218
|
+
const os = await import("os");
|
|
219
|
+
return os.hostname() || "unknown-host";
|
|
220
|
+
} catch {
|
|
221
|
+
return "unknown-host";
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/stacktrace.ts
|
|
226
|
+
var MAX_FRAMES = 50;
|
|
227
|
+
var MAX_FILENAME_CHARS = 1024;
|
|
228
|
+
var FRAME_RE = /^\s*at\s+(?:async\s+)?(?:(.+?)\s+\()?(.+?)\)?$/;
|
|
229
|
+
var LOCATION_RE = /^(.*?):(\d+):(\d+)$/;
|
|
230
|
+
function isInApp(filename) {
|
|
231
|
+
if (!filename) return false;
|
|
232
|
+
if (filename.startsWith("node:")) return false;
|
|
233
|
+
if (filename.startsWith("internal/")) return false;
|
|
234
|
+
if (filename.includes("node_modules")) return false;
|
|
235
|
+
if (filename === "<anonymous>") return false;
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
function parseStacktrace(stack) {
|
|
239
|
+
if (!stack) return [];
|
|
240
|
+
const frames = [];
|
|
241
|
+
for (const line of stack.split("\n")) {
|
|
242
|
+
if (frames.length >= MAX_FRAMES) break;
|
|
243
|
+
const match = FRAME_RE.exec(line);
|
|
244
|
+
if (!match || !line.trimStart().startsWith("at ")) continue;
|
|
245
|
+
const [, fn, location] = match;
|
|
246
|
+
const loc = LOCATION_RE.exec(location ?? "");
|
|
247
|
+
const filename = loc ? loc[1] : location;
|
|
248
|
+
frames.push({
|
|
249
|
+
function: fn || "<anonymous>",
|
|
250
|
+
filename: filename ? filename.slice(0, MAX_FILENAME_CHARS) : void 0,
|
|
251
|
+
lineno: loc?.[2] ? Number(loc[2]) : void 0,
|
|
252
|
+
colno: loc?.[3] ? Number(loc[3]) : void 0,
|
|
253
|
+
in_app: isInApp(filename)
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
return frames;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/scrub.ts
|
|
260
|
+
var SECRET_PATTERNS = [
|
|
261
|
+
// client secret da Hobots
|
|
262
|
+
[/hb_[a-f0-9]{16,}/gi, "[REDACTED]"],
|
|
263
|
+
// tokens bearer em headers/mensagens
|
|
264
|
+
[/\b(authorization:?\s*)?bearer\s+[a-z0-9._~+/=-]{8,}/gi, "Bearer [REDACTED]"],
|
|
265
|
+
// chaves no formato sk-… (OpenAI e afins)
|
|
266
|
+
[/\bsk-[a-z0-9_-]{16,}\b/gi, "[REDACTED]"],
|
|
267
|
+
// credenciais embutidas em URL (https://user:pass@host)
|
|
268
|
+
[/\/\/[^/\s:@]+:[^/\s@]+@/g, "//[REDACTED]@"]
|
|
269
|
+
];
|
|
270
|
+
function scrubSecrets(text) {
|
|
271
|
+
let out = text;
|
|
272
|
+
for (const [pattern, replacement] of SECRET_PATTERNS) {
|
|
273
|
+
out = out.replace(pattern, replacement);
|
|
274
|
+
}
|
|
275
|
+
return out;
|
|
276
|
+
}
|
|
277
|
+
|
|
208
278
|
// src/eventbuilder.ts
|
|
209
279
|
var MAX_EXCEPTION_VALUE_CHARS = 8192;
|
|
210
280
|
function eventFromException(error, type = "error") {
|
|
@@ -349,13 +419,26 @@ var MAX_FINGERPRINT_ITEMS = 10;
|
|
|
349
419
|
var MAX_FINGERPRINT_ITEM_CHARS = 255;
|
|
350
420
|
var MAX_TRANSACTION_NAME_CHARS = 500;
|
|
351
421
|
var MAX_STEPS = 200;
|
|
422
|
+
var MAX_STEP_DESCRIPTION_CHARS = 2048;
|
|
423
|
+
var MAX_STEP_OP_CHARS = 255;
|
|
352
424
|
function clipTags(tags) {
|
|
353
425
|
return Object.fromEntries(Object.entries(tags).map(([key, value]) => [key, truncate(String(value), MAX_TAG_VALUE_CHARS)]));
|
|
354
426
|
}
|
|
427
|
+
function clipStep(step) {
|
|
428
|
+
return {
|
|
429
|
+
...step,
|
|
430
|
+
...step.op ? { op: clip(step.op, MAX_STEP_OP_CHARS) } : {},
|
|
431
|
+
...step.description ? { description: clip(step.description, MAX_STEP_DESCRIPTION_CHARS) } : {}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
355
434
|
function clipFingerprint(fingerprint) {
|
|
356
435
|
if (fingerprint === void 0) return void 0;
|
|
357
436
|
return fingerprint.slice(0, MAX_FINGERPRINT_ITEMS).map((item) => truncate(String(item), MAX_FINGERPRINT_ITEM_CHARS));
|
|
358
437
|
}
|
|
438
|
+
var captured = /* @__PURE__ */ new WeakSet();
|
|
439
|
+
function wasCaptured(error) {
|
|
440
|
+
return typeof error === "object" && error !== null && captured.has(error);
|
|
441
|
+
}
|
|
359
442
|
function runtimeContexts() {
|
|
360
443
|
if (isNode()) {
|
|
361
444
|
return {
|
|
@@ -406,11 +489,13 @@ var Client = class {
|
|
|
406
489
|
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
|
407
490
|
return `Client { baseUrl: '${this.#config.baseUrl}' }`;
|
|
408
491
|
}
|
|
409
|
-
|
|
410
|
-
|
|
492
|
+
/** `contexts` é interno ao SDK (ex.: `contexts.run` com o print do captureException) — o evento ainda pode sobrescrever. */
|
|
493
|
+
captureException(error, hint, contexts) {
|
|
494
|
+
if (typeof error === "object" && error !== null) captured.add(error);
|
|
495
|
+
return this.captureEvent(eventFromException(error, hint?.type ?? "error"), hint, contexts);
|
|
411
496
|
}
|
|
412
|
-
captureEvent(event, hint) {
|
|
413
|
-
const final = this.prepareEvent(event, hint);
|
|
497
|
+
captureEvent(event, hint, contexts) {
|
|
498
|
+
const final = this.prepareEvent(contexts ? { ...event, contexts: { ...contexts, ...event.contexts } } : event, hint);
|
|
414
499
|
this.transport.enqueue({ type: "event", payload: final });
|
|
415
500
|
return final.event_id;
|
|
416
501
|
}
|
|
@@ -433,14 +518,15 @@ var Client = class {
|
|
|
433
518
|
sendTransaction(payload) {
|
|
434
519
|
let steps = payload.steps;
|
|
435
520
|
if (steps.length > MAX_STEPS) {
|
|
436
|
-
this.logger.warn(`transaction com ${steps.length}
|
|
521
|
+
this.logger.warn(`transaction com ${steps.length} etapas \u2014 enviando as primeiras ${MAX_STEPS}`);
|
|
437
522
|
steps = steps.slice(0, MAX_STEPS);
|
|
438
523
|
}
|
|
524
|
+
steps = steps.map(clipStep);
|
|
439
525
|
this.transport.enqueue({
|
|
440
526
|
type: "transaction",
|
|
441
527
|
payload: {
|
|
442
528
|
...payload,
|
|
443
|
-
name:
|
|
529
|
+
name: clip(payload.name || "(sem nome)", MAX_TRANSACTION_NAME_CHARS),
|
|
444
530
|
steps,
|
|
445
531
|
release: payload.release ?? this.options.release,
|
|
446
532
|
environment: this.resolveEnvironment(payload.environment),
|
|
@@ -514,12 +600,12 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
514
600
|
const origin = fromOption ? "clientSecret" : `env ${CLIENT_SECRET_ENV}`;
|
|
515
601
|
if (!key) {
|
|
516
602
|
throw new Error(
|
|
517
|
-
`${prefix} Client Secret ausente \u2014 defina a env ${CLIENT_SECRET_ENV} (ou passe clientSecret nas op\xE7\xF5es do register/start); copie o Client Secret
|
|
603
|
+
`${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`
|
|
518
604
|
);
|
|
519
605
|
}
|
|
520
606
|
if (!SECRET_PATTERN.test(key)) {
|
|
521
607
|
throw new Error(
|
|
522
|
-
`${prefix} ${origin} com formato inv\xE1lido (${key.length} caracteres) \u2014 esperado "hb_" + 64 hex; copie o Client Secret
|
|
608
|
+
`${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`
|
|
523
609
|
);
|
|
524
610
|
}
|
|
525
611
|
const baseUrl = validateBaseUrl(envBaseUrl() ?? DEFAULT_BASE_URL, prefix);
|
|
@@ -531,31 +617,6 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
531
617
|
};
|
|
532
618
|
}
|
|
533
619
|
|
|
534
|
-
// src/env.ts
|
|
535
|
-
var INSTANCE_ID_ENV = "HOBOTS_INSTANCE_ID";
|
|
536
|
-
var ENVIRONMENT_ENV = "HOBOTS_ENVIRONMENT";
|
|
537
|
-
function resolveInstanceId(option) {
|
|
538
|
-
const fromOption = option?.trim();
|
|
539
|
-
if (fromOption) return fromOption;
|
|
540
|
-
if (typeof process === "undefined" || !process.env) return void 0;
|
|
541
|
-
return process.env[INSTANCE_ID_ENV]?.trim() || void 0;
|
|
542
|
-
}
|
|
543
|
-
function resolveEnvironmentOption(option) {
|
|
544
|
-
const fromOption = option?.trim();
|
|
545
|
-
if (fromOption) return fromOption;
|
|
546
|
-
if (typeof process === "undefined" || !process.env) return void 0;
|
|
547
|
-
return process.env[ENVIRONMENT_ENV]?.trim() || void 0;
|
|
548
|
-
}
|
|
549
|
-
async function fallbackInstanceId() {
|
|
550
|
-
if (!isNode()) return "browser";
|
|
551
|
-
try {
|
|
552
|
-
const os = await import("os");
|
|
553
|
-
return os.hostname() || "unknown-host";
|
|
554
|
-
} catch {
|
|
555
|
-
return "unknown-host";
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
620
|
// src/heartbeat.ts
|
|
560
621
|
var DEFAULT_INTERVAL_MS = WIRE_LIMITS.HEARTBEAT_DEFAULT_INTERVAL_MS;
|
|
561
622
|
var MIN_REPORTED_INTERVAL_MS = WIRE_LIMITS.HEARTBEAT_MIN_INTERVAL_MS;
|
|
@@ -917,6 +978,42 @@ var TasksApi = class {
|
|
|
917
978
|
created_at: typeof body.created_at === "string" ? body.created_at : (/* @__PURE__ */ new Date()).toISOString()
|
|
918
979
|
};
|
|
919
980
|
}
|
|
981
|
+
/**
|
|
982
|
+
* Anexa o processo a uma run já reivindicada para esta instância (env HOBOTS_RUN_ID):
|
|
983
|
+
* POST /v1/runs/:id/start. Um request só — quem decide o retry é o AttachedRunner.
|
|
984
|
+
* 404 (não existe) e 409 (cancelada, já iniciada ou de outra instância) viram
|
|
985
|
+
* TasksApiError com o status.
|
|
986
|
+
*/
|
|
987
|
+
async startRun(runId, request, signal) {
|
|
988
|
+
const { signal: timeoutSignal, cleanup } = withTimeoutSignal(UPDATE_TIMEOUT_MS, signal);
|
|
989
|
+
let response;
|
|
990
|
+
try {
|
|
991
|
+
response = await fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/start`, {
|
|
992
|
+
redirect: "error",
|
|
993
|
+
method: "POST",
|
|
994
|
+
headers: this.headers(),
|
|
995
|
+
body: JSON.stringify(request),
|
|
996
|
+
signal: timeoutSignal
|
|
997
|
+
});
|
|
998
|
+
} finally {
|
|
999
|
+
cleanup();
|
|
1000
|
+
}
|
|
1001
|
+
if (!response.ok) {
|
|
1002
|
+
throw new TasksApiError(`in\xEDcio da run falhou (HTTP ${response.status})`, response.status);
|
|
1003
|
+
}
|
|
1004
|
+
const body = await response.json().catch(() => null);
|
|
1005
|
+
if (typeof body?.id !== "string") {
|
|
1006
|
+
throw new TasksApiError("resposta inv\xE1lida do in\xEDcio da run (id ausente)", 0);
|
|
1007
|
+
}
|
|
1008
|
+
const form = body.form;
|
|
1009
|
+
return {
|
|
1010
|
+
id: body.id,
|
|
1011
|
+
params: body.params && typeof body.params === "object" && !Array.isArray(body.params) ? body.params : {},
|
|
1012
|
+
form: typeof form?.id === "string" && typeof form.name === "string" ? { id: form.id, name: form.name } : null,
|
|
1013
|
+
source: typeof body.source === "string" ? body.source : "hobots",
|
|
1014
|
+
created_at: typeof body.created_at === "string" ? body.created_at : (/* @__PURE__ */ new Date()).toISOString()
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
920
1017
|
/** Nunca confia no shape da resposta: runs malformadas são descartadas e o `max` é respeitado. */
|
|
921
1018
|
parsePollResponse(body, max) {
|
|
922
1019
|
const runs = body?.runs;
|
|
@@ -1174,7 +1271,7 @@ var Backoff = class {
|
|
|
1174
1271
|
this.authFailures++;
|
|
1175
1272
|
if (this.authFailures === 1 || this.authFailures % 10 === 0) {
|
|
1176
1273
|
this.logger.error(
|
|
1177
|
-
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
|
|
1274
|
+
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"
|
|
1178
1275
|
);
|
|
1179
1276
|
}
|
|
1180
1277
|
return Math.max(this.floorMs, AUTH_FAILURE_INTERVAL_MS);
|
|
@@ -1200,6 +1297,9 @@ var TERMINAL_ERROR_NAMES = /* @__PURE__ */ new Set(["RunCanceled", "AbortError",
|
|
|
1200
1297
|
function isTerminalError(error) {
|
|
1201
1298
|
return error instanceof Error && TERMINAL_ERROR_NAMES.has(error.name);
|
|
1202
1299
|
}
|
|
1300
|
+
function isCancellation(error) {
|
|
1301
|
+
return error instanceof Error && (error.name === "RunCanceled" || error.name === "AbortError");
|
|
1302
|
+
}
|
|
1203
1303
|
|
|
1204
1304
|
// src/tasks/each.ts
|
|
1205
1305
|
function thrownMessage(error) {
|
|
@@ -1226,11 +1326,11 @@ var ItemHandle = class {
|
|
|
1226
1326
|
}
|
|
1227
1327
|
failed(message, payload, options) {
|
|
1228
1328
|
this.record("failed", message, payload, options?.image);
|
|
1229
|
-
if (this.step && this.step.status !== "error") this.step.
|
|
1329
|
+
if (this.step && this.step.status !== "error") this.step._setStatus("error", message);
|
|
1230
1330
|
}
|
|
1231
1331
|
occurrence(message, payload, options) {
|
|
1232
1332
|
this.record("occurrence", message, payload, options?.image);
|
|
1233
|
-
if (this.step && this.step.status === "ok") this.step.
|
|
1333
|
+
if (this.step && this.step.status === "ok") this.step._setStatus("warning", message);
|
|
1234
1334
|
}
|
|
1235
1335
|
record(status, message, payload, image3) {
|
|
1236
1336
|
this.reported = true;
|
|
@@ -1261,24 +1361,24 @@ async function eachItem(list, handler, options, sink) {
|
|
|
1261
1361
|
const step = sink.startStep(options.op ?? "item", description);
|
|
1262
1362
|
const handle = new ItemHandle(index, id, step, options.payload?.(element, index), sink);
|
|
1263
1363
|
try {
|
|
1264
|
-
await handler(element, handle, index);
|
|
1364
|
+
await sink.stepScope(step, async () => handler(element, handle, index));
|
|
1265
1365
|
if (!handle.reported) handle.succeeded();
|
|
1266
1366
|
} catch (error) {
|
|
1267
1367
|
if (isTerminalError(error) || sink.isAborted()) {
|
|
1268
|
-
step?.
|
|
1269
|
-
step?.finish();
|
|
1368
|
+
step?._setStatus("cancelled")._close();
|
|
1270
1369
|
throw error;
|
|
1271
1370
|
}
|
|
1272
1371
|
const message = thrownMessage(error);
|
|
1273
1372
|
if (!handle.reported) handle.failed(message);
|
|
1274
|
-
step?.
|
|
1373
|
+
step?._failed(error);
|
|
1275
1374
|
sink.logError(`item ${description} falhou: ${message}`);
|
|
1276
1375
|
}
|
|
1277
|
-
step?.
|
|
1376
|
+
step?._close();
|
|
1278
1377
|
if (options.progress !== false) sink.advance();
|
|
1279
1378
|
result.total += 1;
|
|
1280
1379
|
result[handle.status ?? "succeeded"] += 1;
|
|
1281
1380
|
index += 1;
|
|
1381
|
+
if (options.flush !== false) await sink.flush();
|
|
1282
1382
|
}
|
|
1283
1383
|
return result;
|
|
1284
1384
|
}
|
|
@@ -1318,6 +1418,11 @@ function isInsideDir(root, candidate, sep) {
|
|
|
1318
1418
|
function unavailableLocal(ref) {
|
|
1319
1419
|
return new Error(`arquivo "${ref.name}" enviado pelo Studio de outro computador ou j\xE1 consumido`);
|
|
1320
1420
|
}
|
|
1421
|
+
function localRunPath(ref) {
|
|
1422
|
+
if (!isLocalRun()) return void 0;
|
|
1423
|
+
const path = ref.path;
|
|
1424
|
+
return typeof path === "string" && path.trim() !== "" ? path : void 0;
|
|
1425
|
+
}
|
|
1321
1426
|
function errorCode(error) {
|
|
1322
1427
|
return error?.code;
|
|
1323
1428
|
}
|
|
@@ -1485,8 +1590,16 @@ var RunFileStore = class {
|
|
|
1485
1590
|
this.deps.warn(`arquivo "${ref.name}" baixado, mas a confirma\xE7\xE3o ao servidor falhou: ${String(error)}`);
|
|
1486
1591
|
}
|
|
1487
1592
|
}
|
|
1488
|
-
/**
|
|
1593
|
+
/**
|
|
1594
|
+
* storage 'local': copia o único arquivo de `$HOBOTS_LOCAL_FILES_DIR/<id>/` e apaga a pasta de
|
|
1595
|
+
* origem. No modo local, um FileRef com `path` copia direto desse caminho, sem apagar a origem.
|
|
1596
|
+
*/
|
|
1489
1597
|
async copyLocal(ref, target) {
|
|
1598
|
+
const direct = localRunPath(ref);
|
|
1599
|
+
if (direct !== void 0) {
|
|
1600
|
+
await this.copyFromPath(ref, direct, target);
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1490
1603
|
const base = process.env[LOCAL_FILES_DIR_ENV];
|
|
1491
1604
|
if (!base) throw unavailableLocal(ref);
|
|
1492
1605
|
const { copyFile, readdir, rename, rm, stat } = await import("fs/promises");
|
|
@@ -1520,6 +1633,26 @@ var RunFileStore = class {
|
|
|
1520
1633
|
}
|
|
1521
1634
|
await rm(folder, { recursive: true, force: true });
|
|
1522
1635
|
}
|
|
1636
|
+
/**
|
|
1637
|
+
* Modo local: cópia do arquivo apontado por `path` (teste roda várias vezes — a fonte fica).
|
|
1638
|
+
* Sem conferência de tamanho: o `size` do FileRef é informativo aqui.
|
|
1639
|
+
*/
|
|
1640
|
+
async copyFromPath(ref, path, target) {
|
|
1641
|
+
const { copyFile, rename, rm, stat } = await import("fs/promises");
|
|
1642
|
+
const { resolve: resolve2 } = await import("path");
|
|
1643
|
+
const source = resolve2(path);
|
|
1644
|
+
const info = await stat(source).catch(() => void 0);
|
|
1645
|
+
if (!info?.isFile()) throw new Error(`arquivo "${ref.name}" n\xE3o encontrado em ${path}`);
|
|
1646
|
+
this.checkCanceled();
|
|
1647
|
+
const part = `${target}.part`;
|
|
1648
|
+
try {
|
|
1649
|
+
await copyFile(source, part);
|
|
1650
|
+
await rename(part, target);
|
|
1651
|
+
} catch (error) {
|
|
1652
|
+
await rm(part, { force: true });
|
|
1653
|
+
throw error;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1523
1656
|
translate(error, ref) {
|
|
1524
1657
|
if (this.deps.signal.aborted) return runCanceledError();
|
|
1525
1658
|
if (error instanceof TasksApiError && error.status === 410) {
|
|
@@ -1648,6 +1781,11 @@ function printRunLog(level, message) {
|
|
|
1648
1781
|
const method2 = level === "warning" ? "warn" : level === "success" ? "info" : level;
|
|
1649
1782
|
console[method2](line, message);
|
|
1650
1783
|
}
|
|
1784
|
+
var PROGRESS_LINE_PREFIX = "[hobots:progress]";
|
|
1785
|
+
var PROGRESS_PRINT_THROTTLE_MS = 250;
|
|
1786
|
+
function printRunProgress(current, total) {
|
|
1787
|
+
console.log(`${PROGRESS_LINE_PREFIX} ${current}/${total}`);
|
|
1788
|
+
}
|
|
1651
1789
|
function cancelableSleep(ms, signal) {
|
|
1652
1790
|
return new Promise((resolve2, reject) => {
|
|
1653
1791
|
if (signal.aborted) {
|
|
@@ -1716,14 +1854,16 @@ var RunReporter = class {
|
|
|
1716
1854
|
tx.setStepGuard(checkCanceled);
|
|
1717
1855
|
const eachSink = {
|
|
1718
1856
|
report: (status, message, payload, id, image3) => this.addItem(status, message, payload, id, image3),
|
|
1719
|
-
startStep: (op, description) => tx.
|
|
1857
|
+
startStep: (op, description) => tx.start(op, description),
|
|
1858
|
+
stepScope: (step, fn) => tx.scope(step, fn),
|
|
1720
1859
|
declareTotal: (total) => {
|
|
1721
1860
|
if (this.progressTotal == null) this.setProgress(this.progressCurrent, total);
|
|
1722
1861
|
},
|
|
1723
1862
|
advance: () => this.setProgress(this.progressCurrent + 1, this.progressTotal),
|
|
1724
1863
|
checkCanceled,
|
|
1725
1864
|
isAborted: () => this.abortController.signal.aborted,
|
|
1726
|
-
logError: (message) => this.log("error", message)
|
|
1865
|
+
logError: (message) => this.log("error", message),
|
|
1866
|
+
flush: () => this.flush()
|
|
1727
1867
|
};
|
|
1728
1868
|
const items = {
|
|
1729
1869
|
add: (item) => {
|
|
@@ -1778,7 +1918,14 @@ var RunReporter = class {
|
|
|
1778
1918
|
items,
|
|
1779
1919
|
progress,
|
|
1780
1920
|
files: this.fileStore.files,
|
|
1781
|
-
|
|
1921
|
+
step: async (name, fn, options) => {
|
|
1922
|
+
const root = tx.currentStep() === void 0;
|
|
1923
|
+
try {
|
|
1924
|
+
return await tx.step(name, fn, { ...options, signal, wait: sleep2, warn: (message) => this.log("warning", message) });
|
|
1925
|
+
} finally {
|
|
1926
|
+
if (options?.flush !== false && root && !signal.aborted) await this.flush();
|
|
1927
|
+
}
|
|
1928
|
+
},
|
|
1782
1929
|
flush: (timeoutMs) => this.flush(timeoutMs),
|
|
1783
1930
|
signal,
|
|
1784
1931
|
throwIfCanceled: checkCanceled,
|
|
@@ -1809,8 +1956,17 @@ var RunReporter = class {
|
|
|
1809
1956
|
itemSeq = 0;
|
|
1810
1957
|
/** Servidor sem o recurso (404) — para de tentar (e de avisar) nesta run. */
|
|
1811
1958
|
itemsUnsupported = false;
|
|
1812
|
-
/**
|
|
1959
|
+
/**
|
|
1960
|
+
* Cota de itens por run do servidor (429, legado — hoje a api não tem teto de itens) —
|
|
1961
|
+
* degrada com um aviso só.
|
|
1962
|
+
*/
|
|
1813
1963
|
itemsQuotaExceeded = false;
|
|
1964
|
+
/**
|
|
1965
|
+
* Armazenamento do plano cheio (402 `storage_limit`): o lote é descartado e o aviso sai
|
|
1966
|
+
* uma vez por episódio. Os envios continuam — num agente `start()` o espaço pode ser
|
|
1967
|
+
* liberado (ou o plano trocado) sem reiniciar; um lote aceito rearma o aviso.
|
|
1968
|
+
*/
|
|
1969
|
+
itemsStorageFull = false;
|
|
1814
1970
|
/**
|
|
1815
1971
|
* Só o kill() do exit path fecha os itens — `dead` (run cancelada/finalizada no
|
|
1816
1972
|
* servidor) NÃO fecha: o servidor aceita itens de run terminal (trabalho já feito).
|
|
@@ -1832,6 +1988,8 @@ var RunReporter = class {
|
|
|
1832
1988
|
progressDirty = false;
|
|
1833
1989
|
progressTimer;
|
|
1834
1990
|
progressWarned = false;
|
|
1991
|
+
/** Relógio do último eco `[hobots:progress]` no terminal (throttle do eco, não do wire). */
|
|
1992
|
+
progressLastPrint = 0;
|
|
1835
1993
|
/** Arquivos do formulário desta run (`ctx.files`) — a pasta temporária é apagada no fim. */
|
|
1836
1994
|
fileStore;
|
|
1837
1995
|
/** Apaga as cópias de arquivo que ficaram na pasta temporária da run. Nunca lança. */
|
|
@@ -1882,15 +2040,17 @@ var RunReporter = class {
|
|
|
1882
2040
|
/**
|
|
1883
2041
|
* Linha de log da run com eco no terminal. `image` (opcional) vira o anexo da linha:
|
|
1884
2042
|
* o id entra na entrada ANTES de qualquer I/O — é isso que mantém a ordem dos logs
|
|
1885
|
-
* intacta e o handler livre da latência do upload.
|
|
2043
|
+
* intacta e o handler livre da latência do upload. Devolve o id do anexo (quando houve
|
|
2044
|
+
* imagem e ela não foi descartada) — o `captureException` o leva ao evento da issue.
|
|
1886
2045
|
*/
|
|
1887
2046
|
log(level, message, image3) {
|
|
1888
|
-
if (this.dead) return;
|
|
2047
|
+
if (this.dead) return void 0;
|
|
1889
2048
|
const scrubbed = scrubSecrets(safeString(message));
|
|
1890
2049
|
const ts = nowISO();
|
|
1891
2050
|
const attachmentId = image3 === void 0 ? void 0 : this.queueAttachment(image3, ts);
|
|
1892
2051
|
printRunLog(level, attachmentId ? `${scrubbed} [imagem]` : scrubbed);
|
|
1893
2052
|
this.push(level, scrubbed, true, attachmentId ? [attachmentId] : void 0, ts);
|
|
2053
|
+
return attachmentId;
|
|
1894
2054
|
}
|
|
1895
2055
|
/** Bufferiza uma entrada de log sem eco no terminal (cap, truncate, flush). */
|
|
1896
2056
|
push(level, message, alreadyScrubbed = false, attachments, ts = nowISO()) {
|
|
@@ -1936,7 +2096,7 @@ var RunReporter = class {
|
|
|
1936
2096
|
}
|
|
1937
2097
|
}
|
|
1938
2098
|
/**
|
|
1939
|
-
* `hobots.flush()`: o bot decide quando entregar o que já fez —
|
|
2099
|
+
* `hobots.flush()`: o bot decide quando entregar o que já fez — etapas raiz finalizadas
|
|
1940
2100
|
* viram transactions parciais da run, e itens, logs e progresso pendentes saem já, sem
|
|
1941
2101
|
* esperar o throttle. Aguarda a entrega com teto. Nunca lança (pode ir num `finally`).
|
|
1942
2102
|
*/
|
|
@@ -1994,10 +2154,16 @@ var RunReporter = class {
|
|
|
1994
2154
|
}
|
|
1995
2155
|
const safeCurrent = Number.isFinite(current) ? Math.max(0, Math.floor(current)) : this.progressCurrent;
|
|
1996
2156
|
const next = total == null ? safeCurrent : Math.min(safeCurrent, total);
|
|
1997
|
-
|
|
2157
|
+
const totalChanged = total !== this.progressTotal;
|
|
2158
|
+
if (next === this.progressCurrent && !totalChanged) return;
|
|
1998
2159
|
this.progressCurrent = next;
|
|
1999
2160
|
this.progressTotal = total;
|
|
2000
2161
|
if (total == null) return;
|
|
2162
|
+
const now = Date.now();
|
|
2163
|
+
if (totalChanged || next >= total || now - this.progressLastPrint >= PROGRESS_PRINT_THROTTLE_MS) {
|
|
2164
|
+
this.progressLastPrint = now;
|
|
2165
|
+
printRunProgress(next, total);
|
|
2166
|
+
}
|
|
2001
2167
|
this.progressDirty = true;
|
|
2002
2168
|
if (this.progressTimer) return;
|
|
2003
2169
|
this.progressTimer = setTimeout(() => {
|
|
@@ -2120,6 +2286,7 @@ var RunReporter = class {
|
|
|
2120
2286
|
if (this.itemsUnsupported) return;
|
|
2121
2287
|
try {
|
|
2122
2288
|
await this.api.createItems(this.runId, { items: batch });
|
|
2289
|
+
this.itemsStorageFull = false;
|
|
2123
2290
|
} catch (error) {
|
|
2124
2291
|
if (error instanceof TasksApiError && error.status === 404) {
|
|
2125
2292
|
this.itemsUnsupported = true;
|
|
@@ -2133,6 +2300,13 @@ var RunReporter = class {
|
|
|
2133
2300
|
}
|
|
2134
2301
|
return;
|
|
2135
2302
|
}
|
|
2303
|
+
if (error instanceof TasksApiError && error.status === 402) {
|
|
2304
|
+
if (!this.itemsStorageFull) {
|
|
2305
|
+
this.itemsStorageFull = true;
|
|
2306
|
+
this.log("warning", "itens descartados: armazenamento do plano cheio \u2014 libere espa\xE7o ou fa\xE7a upgrade em Plano e cobran\xE7a");
|
|
2307
|
+
}
|
|
2308
|
+
return;
|
|
2309
|
+
}
|
|
2136
2310
|
if (error instanceof TasksApiError) {
|
|
2137
2311
|
this.logger.error(`lote de ${batch.length} item(ns) rejeitado pelo servidor:`, error);
|
|
2138
2312
|
this.log("warning", `${batch.length} item(ns) descartado(s): envio rejeitado pelo servidor (HTTP ${error.status})`);
|
|
@@ -2164,7 +2338,7 @@ var RunReporter = class {
|
|
|
2164
2338
|
}
|
|
2165
2339
|
// ── Anexos ───────────────────────────────────────────────────────────────────
|
|
2166
2340
|
/**
|
|
2167
|
-
* Pendura uma imagem num evento da run (log, item ou
|
|
2341
|
+
* Pendura uma imagem num evento da run (log, item ou etapa): faz as checagens que não
|
|
2168
2342
|
* dependem de I/O (reporter morto, instalação sem anexos, cota), reserva o id e dispara
|
|
2169
2343
|
* o upload em segundo plano. Devolve o id — já referenciável pelo evento — ou undefined
|
|
2170
2344
|
* quando a imagem foi descartada (sempre com aviso no log da run). Nunca lança: todo
|
|
@@ -2337,11 +2511,13 @@ var RunReporter = class {
|
|
|
2337
2511
|
|
|
2338
2512
|
// src/tasks/run-context-store.ts
|
|
2339
2513
|
var storage;
|
|
2514
|
+
var stepStorage;
|
|
2340
2515
|
var initPromise;
|
|
2341
2516
|
async function ensureStorage() {
|
|
2342
2517
|
if (storage || !isNode()) return;
|
|
2343
2518
|
initPromise ??= import("async_hooks").then(({ AsyncLocalStorage: ALS }) => {
|
|
2344
2519
|
storage = new ALS();
|
|
2520
|
+
stepStorage = new ALS();
|
|
2345
2521
|
});
|
|
2346
2522
|
await initPromise;
|
|
2347
2523
|
}
|
|
@@ -2352,12 +2528,23 @@ async function runWithRunContext(store, fn) {
|
|
|
2352
2528
|
function currentRunReporter() {
|
|
2353
2529
|
return storage?.getStore()?.reporter;
|
|
2354
2530
|
}
|
|
2531
|
+
async function runWithStepScope(scope, fn) {
|
|
2532
|
+
await ensureStorage();
|
|
2533
|
+
return stepStorage ? stepStorage.run(scope, fn) : fn();
|
|
2534
|
+
}
|
|
2535
|
+
function currentStep(transaction) {
|
|
2536
|
+
const scope = stepStorage?.getStore();
|
|
2537
|
+
return scope?.transaction === transaction ? scope.step : void 0;
|
|
2538
|
+
}
|
|
2355
2539
|
|
|
2356
2540
|
// src/transaction.ts
|
|
2357
2541
|
var FLUSH_TRANSACTION_OP = "hobots.run.flush";
|
|
2542
|
+
var DEFAULT_STEP_RETRIES = 1;
|
|
2543
|
+
var DEFAULT_STEP_RETRY_DELAY_MS = 5e3;
|
|
2358
2544
|
var MAX_STEP_MESSAGE_CHARS = 2e3;
|
|
2545
|
+
var MAX_BACKLOG = 100;
|
|
2359
2546
|
function stepMessage(text) {
|
|
2360
|
-
return
|
|
2547
|
+
return clip(scrubSecrets(text), MAX_STEP_MESSAGE_CHARS);
|
|
2361
2548
|
}
|
|
2362
2549
|
function stepId() {
|
|
2363
2550
|
return uuid4().slice(0, 16);
|
|
@@ -2365,6 +2552,13 @@ function stepId() {
|
|
|
2365
2552
|
function stepLabel(op, description) {
|
|
2366
2553
|
return [op, description].filter(Boolean).join(" \u2014 ") || "(sem nome)";
|
|
2367
2554
|
}
|
|
2555
|
+
function errorText(error) {
|
|
2556
|
+
if (error instanceof Error) return error.message || error.name;
|
|
2557
|
+
return safeString(error);
|
|
2558
|
+
}
|
|
2559
|
+
function errorReason(error) {
|
|
2560
|
+
return error instanceof Error ? `${error.name}: ${errorText(error)}` : errorText(error);
|
|
2561
|
+
}
|
|
2368
2562
|
function levelForStatus(status) {
|
|
2369
2563
|
switch (status) {
|
|
2370
2564
|
case "warning":
|
|
@@ -2404,58 +2598,68 @@ var Step = class {
|
|
|
2404
2598
|
startTimestamp = nowISO();
|
|
2405
2599
|
status = "ok";
|
|
2406
2600
|
endTimestamp;
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
* `options.image` pendura um print do estado da tela em `data.attachments`.
|
|
2412
|
-
*/
|
|
2413
|
-
setStatus(status, message, options) {
|
|
2414
|
-
this.status = status;
|
|
2415
|
-
if (message) this.setData("message", stepMessage(message));
|
|
2601
|
+
_data;
|
|
2602
|
+
// ── API do bot ────────────────────────────────────────────────────────────
|
|
2603
|
+
warning(message, options) {
|
|
2604
|
+
if (this.status !== "error") this._setStatus("warning", message);
|
|
2416
2605
|
if (options?.image !== void 0) this.attachImage(options.image);
|
|
2417
2606
|
return this;
|
|
2418
2607
|
}
|
|
2608
|
+
image(image3) {
|
|
2609
|
+
this.attachImage(image3);
|
|
2610
|
+
return this;
|
|
2611
|
+
}
|
|
2612
|
+
data(key, value) {
|
|
2613
|
+
if (key === "attachments") return this;
|
|
2614
|
+
this._setData(key, value);
|
|
2615
|
+
return this;
|
|
2616
|
+
}
|
|
2617
|
+
// ── Internos (chamados pelo SDK) ──────────────────────────────────────────
|
|
2618
|
+
/** `data` da etapa como vai no wire (undefined quando vazio). */
|
|
2619
|
+
get payload() {
|
|
2620
|
+
return this._data;
|
|
2621
|
+
}
|
|
2622
|
+
_setStatus(status, message) {
|
|
2623
|
+
this.status = status;
|
|
2624
|
+
if (message) this._setData("message", stepMessage(safeString(message)));
|
|
2625
|
+
return this;
|
|
2626
|
+
}
|
|
2419
2627
|
/**
|
|
2420
|
-
* Marca
|
|
2421
|
-
*
|
|
2422
|
-
* `options.image` pendura um print do estado da tela em `data.attachments`.
|
|
2628
|
+
* Marca `error` guardando `{ type, message }` em `data.error` e fecha a etapa. Aceita qualquer
|
|
2629
|
+
* valor lançado; `message` sobrescreve a mensagem do erro. Numa etapa já fechada não faz nada.
|
|
2423
2630
|
*/
|
|
2424
|
-
|
|
2631
|
+
_failed(error, message) {
|
|
2632
|
+
if (this.endTimestamp) return this;
|
|
2425
2633
|
this.status = "error";
|
|
2426
2634
|
const type = error instanceof Error ? error.name : typeof error === "object" && error !== null ? "Error" : void 0;
|
|
2427
|
-
const raw = message ?? (error
|
|
2428
|
-
this.
|
|
2429
|
-
|
|
2635
|
+
const raw = message ?? errorText(error);
|
|
2636
|
+
this._setData("error", { ...type ? { type } : {}, message: stepMessage(safeString(raw)) });
|
|
2637
|
+
this._close();
|
|
2430
2638
|
return this;
|
|
2431
2639
|
}
|
|
2640
|
+
_setData(key, value) {
|
|
2641
|
+
this._data = { ...this._data, [key]: value };
|
|
2642
|
+
}
|
|
2432
2643
|
/**
|
|
2433
2644
|
* Dispara o upload pela run (sink do RunReporter) e referencia o anexo em
|
|
2434
2645
|
* `data.attachments` (chave reservada; teto igual ao das linhas de log). Fora de uma
|
|
2435
2646
|
* run não há onde pendurar a imagem — descarta com aviso único.
|
|
2436
2647
|
*/
|
|
2437
2648
|
attachImage(image3) {
|
|
2438
|
-
const current = Array.isArray(this.
|
|
2649
|
+
const current = Array.isArray(this._data?.attachments) ? this._data.attachments : [];
|
|
2439
2650
|
if (current.length >= MAX_ATTACHMENTS_PER_LOG_ENTRY) return;
|
|
2440
2651
|
const id = this.transaction.attachImage(image3);
|
|
2441
2652
|
if (!id) return;
|
|
2442
|
-
this.
|
|
2653
|
+
this._setData("attachments", [...current, id]);
|
|
2443
2654
|
}
|
|
2444
|
-
/** Motivo registrado
|
|
2655
|
+
/** Motivo registrado (warning/erro) — usado no log de FINISH. */
|
|
2445
2656
|
reason() {
|
|
2446
|
-
const error = this.
|
|
2447
|
-
const text = typeof error?.message === "string" ? error.message : this.
|
|
2657
|
+
const error = this._data?.error;
|
|
2658
|
+
const text = typeof error?.message === "string" ? error.message : this._data?.message;
|
|
2448
2659
|
return typeof text === "string" && text ? text : void 0;
|
|
2449
2660
|
}
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
return this;
|
|
2453
|
-
}
|
|
2454
|
-
/** Step aninhada (filha desta step). */
|
|
2455
|
-
startChild(op, description) {
|
|
2456
|
-
return this.transaction.createStep(this.stepId, op, description);
|
|
2457
|
-
}
|
|
2458
|
-
finish() {
|
|
2661
|
+
/** Fecha a etapa mantendo o status que estiver. Idempotente. */
|
|
2662
|
+
_close() {
|
|
2459
2663
|
if (this.endTimestamp) return;
|
|
2460
2664
|
this.endTimestamp = nowISO();
|
|
2461
2665
|
this.transaction.emit(
|
|
@@ -2471,22 +2675,23 @@ var Step = class {
|
|
|
2471
2675
|
toJSON() {
|
|
2472
2676
|
return {
|
|
2473
2677
|
step_id: this.stepId,
|
|
2474
|
-
parent_step_id: this.parentStepId,
|
|
2475
|
-
op: this.op,
|
|
2476
|
-
description: this.description,
|
|
2678
|
+
...this.parentStepId ? { parent_step_id: this.parentStepId } : {},
|
|
2679
|
+
...this.op ? { op: clip(this.op, MAX_STEP_OP_CHARS) } : {},
|
|
2680
|
+
...this.description ? { description: clip(this.description, MAX_STEP_DESCRIPTION_CHARS) } : {},
|
|
2477
2681
|
status: this.status,
|
|
2478
2682
|
start_timestamp: this.startTimestamp,
|
|
2479
2683
|
timestamp: this.endTimestamp ?? nowISO(),
|
|
2480
|
-
data: this.
|
|
2684
|
+
...this._data ? { data: this._data } : {}
|
|
2481
2685
|
};
|
|
2482
2686
|
}
|
|
2483
2687
|
};
|
|
2484
2688
|
var Transaction = class {
|
|
2485
|
-
constructor(client, name, op, runId) {
|
|
2689
|
+
constructor(client, name, op, runId, options = {}) {
|
|
2486
2690
|
this.client = client;
|
|
2487
2691
|
this.name = name;
|
|
2488
2692
|
this.op = op;
|
|
2489
2693
|
this.runId = runId;
|
|
2694
|
+
this.echo = options.echo ?? false;
|
|
2490
2695
|
this.emit("transaction", "start", op ? `${name} (${op})` : name);
|
|
2491
2696
|
}
|
|
2492
2697
|
client;
|
|
@@ -2498,33 +2703,36 @@ var Transaction = class {
|
|
|
2498
2703
|
startTimestamp = nowISO();
|
|
2499
2704
|
status = "ok";
|
|
2500
2705
|
steps = [];
|
|
2501
|
-
tags = {};
|
|
2502
2706
|
finished = false;
|
|
2503
2707
|
lifecycleSink;
|
|
2504
2708
|
attachmentSink;
|
|
2505
2709
|
warnedNoAttachmentSink = false;
|
|
2506
|
-
// instalado pelo RunReporter: abrir uma
|
|
2710
|
+
// instalado pelo RunReporter: abrir uma etapa é um ponto de parada cooperativo (lança RunCanceled)
|
|
2507
2711
|
stepGuard;
|
|
2712
|
+
// modo local (sem client): o FINISH de cada etapa sai no terminal, como `[hobots:item]`
|
|
2713
|
+
echo;
|
|
2508
2714
|
// eventos emitidos antes do sink existir (o START da transaction sai no construtor,
|
|
2509
2715
|
// antes do RunReporter conectar) — replay no setLifecycleSink
|
|
2510
2716
|
lifecycleBacklog = [];
|
|
2717
|
+
// ── Lifecycle / eco ───────────────────────────────────────────────────────
|
|
2511
2718
|
/**
|
|
2512
|
-
* Registra o início/fim de uma
|
|
2513
|
-
* lifecycle, se conectado (é assim que os eventos de performance aparecem nos
|
|
2514
|
-
*
|
|
2515
|
-
*
|
|
2719
|
+
* Registra o início/fim de uma etapa ou da transaction: espelha a linha no sink de
|
|
2720
|
+
* lifecycle, se conectado (é assim que os eventos de performance aparecem nos logs da
|
|
2721
|
+
* run no app), e ecoa no terminal com `debug: true` — ou, no modo local, só o FINISH das
|
|
2722
|
+
* etapas (`[hobots:step] FINISH …`), para o Studio e o dev verem.
|
|
2516
2723
|
*/
|
|
2517
2724
|
emit(kind, phase, label, startTs, endTs, status, reason) {
|
|
2518
2725
|
const durationMs = startTs && endTs ? new Date(endTs).getTime() - new Date(startTs).getTime() : void 0;
|
|
2519
2726
|
const suffix = (durationMs != null ? ` (${durationMs}ms)` : "") + (reason ? `: ${reason}` : "");
|
|
2520
|
-
|
|
2727
|
+
const localFinish = this.echo && kind === "step" && phase === "finish";
|
|
2728
|
+
if (this.client?.debug || localFinish) {
|
|
2521
2729
|
const tag = colorize(`[hobots:${kind}] ${phase.toUpperCase()}`, phase === "finish" ? colorForStatus(status ?? "ok") : "cyan");
|
|
2522
2730
|
console.log(`${tag} ${label}${suffix}`);
|
|
2523
2731
|
}
|
|
2524
2732
|
const level = phase === "finish" ? levelForStatus(status ?? "ok") : "info";
|
|
2525
2733
|
const message = `[${kind}] ${phase.toUpperCase()} ${label}${suffix}`;
|
|
2526
2734
|
if (this.lifecycleSink) this.lifecycleSink(level, message);
|
|
2527
|
-
else if (this.lifecycleBacklog.length <
|
|
2735
|
+
else if (this.lifecycleBacklog.length < MAX_BACKLOG) this.lifecycleBacklog.push([level, message]);
|
|
2528
2736
|
}
|
|
2529
2737
|
/** Conecta o espelho de lifecycle (RunReporter) e faz replay do backlog em ordem. */
|
|
2530
2738
|
setLifecycleSink(sink) {
|
|
@@ -2533,59 +2741,111 @@ var Transaction = class {
|
|
|
2533
2741
|
this.lifecycleBacklog = [];
|
|
2534
2742
|
for (const [level, message] of backlog) sink(level, message);
|
|
2535
2743
|
}
|
|
2536
|
-
/** Conecta o pipeline de anexos da run — é o que dá imagem às
|
|
2744
|
+
/** Conecta o pipeline de anexos da run — é o que dá imagem às etapas. */
|
|
2537
2745
|
setAttachmentSink(sink) {
|
|
2538
2746
|
this.attachmentSink = sink;
|
|
2539
2747
|
}
|
|
2540
|
-
/** Imagem de uma
|
|
2748
|
+
/** Imagem de uma etapa → id do anexo pela run; sem run, descarta com aviso único. */
|
|
2541
2749
|
attachImage(image3) {
|
|
2542
2750
|
if (this.attachmentSink) return this.attachmentSink(image3, nowISO());
|
|
2543
2751
|
if (!this.warnedNoAttachmentSink) {
|
|
2544
2752
|
this.warnedNoAttachmentSink = true;
|
|
2545
|
-
console.warn("[hobots] imagem de
|
|
2753
|
+
console.warn("[hobots] imagem de etapa descartada \u2014 s\xF3 \xE9 suportada dentro de uma execu\xE7\xE3o");
|
|
2546
2754
|
}
|
|
2547
2755
|
return void 0;
|
|
2548
2756
|
}
|
|
2757
|
+
// ── Etapas ────────────────────────────────────────────────────────────────
|
|
2549
2758
|
/**
|
|
2550
|
-
* Conecta a checagem de cancelamento chamada antes de cada
|
|
2551
|
-
*
|
|
2759
|
+
* Conecta a checagem de cancelamento chamada antes de cada etapa nova (inclusive antes de
|
|
2760
|
+
* cada nova tentativa) — é assim que `hobots.step()` para uma run cancelada.
|
|
2552
2761
|
*/
|
|
2553
2762
|
setStepGuard(guard) {
|
|
2554
2763
|
this.stepGuard = guard;
|
|
2555
2764
|
}
|
|
2556
|
-
|
|
2765
|
+
/** A etapa aberta por `step()` (ou `items.each`) no contexto async atual, se houver. */
|
|
2766
|
+
currentStep() {
|
|
2767
|
+
return currentStep(this);
|
|
2768
|
+
}
|
|
2769
|
+
/** Faz de `step` a etapa corrente durante `fn` (filhas aninham nela). Sem etapa, só roda `fn`. */
|
|
2770
|
+
scope(step, fn) {
|
|
2771
|
+
if (!step) return fn();
|
|
2772
|
+
return runWithStepScope({ transaction: this, step }, fn);
|
|
2773
|
+
}
|
|
2774
|
+
/** Abre uma etapa sob a corrente (ou direta da transaction). Interno: o bot usa `step()`. */
|
|
2775
|
+
start(op, description) {
|
|
2557
2776
|
this.stepGuard?.();
|
|
2558
|
-
const
|
|
2777
|
+
const parent = this.currentStep();
|
|
2778
|
+
const step = new Step(this, parent ? parent.stepId : this.stepId, op, description);
|
|
2559
2779
|
this.steps.push(step);
|
|
2560
2780
|
this.emit("step", "start", stepLabel(op, description));
|
|
2561
2781
|
return step;
|
|
2562
2782
|
}
|
|
2563
|
-
/**
|
|
2564
|
-
|
|
2565
|
-
|
|
2783
|
+
/**
|
|
2784
|
+
* Registra a etapa `name` executando `fn(etapa)` até dar certo ou esgotar `retries`
|
|
2785
|
+
* tentativas extras (default 1), esperando `retryDelayMs` entre elas (default 5 000).
|
|
2786
|
+
* Resolve com o retorno de `fn`.
|
|
2787
|
+
*
|
|
2788
|
+
* Uma etapa só no waterfall, cobrindo todas as tentativas: `data.attempts` quando houve
|
|
2789
|
+
* mais de uma; `warning` com o motivo quando a última deu certo; `error` (e a exceção
|
|
2790
|
+
* sobe) quando esgotou; `cancelled` em cancelamento/timeout, que nunca repetem.
|
|
2791
|
+
*/
|
|
2792
|
+
async step(name, fn, options = {}) {
|
|
2793
|
+
const requested = options.retries ?? DEFAULT_STEP_RETRIES;
|
|
2794
|
+
const totalRetries = Number.isFinite(requested) ? Math.max(0, Math.floor(requested)) : DEFAULT_STEP_RETRIES;
|
|
2795
|
+
const step = this.start(void 0, safeString(name));
|
|
2796
|
+
let attempts = 0;
|
|
2797
|
+
let lastFailure;
|
|
2798
|
+
try {
|
|
2799
|
+
const result = await retryWithBackoff(
|
|
2800
|
+
(attempt) => {
|
|
2801
|
+
attempts = attempt;
|
|
2802
|
+
return this.scope(step, async () => fn(step));
|
|
2803
|
+
},
|
|
2804
|
+
{
|
|
2805
|
+
retries: totalRetries,
|
|
2806
|
+
delayMs: (attempt) => resolveDelay(options.retryDelayMs, attempt, DEFAULT_STEP_RETRY_DELAY_MS),
|
|
2807
|
+
signal: options.signal,
|
|
2808
|
+
wait: options.wait,
|
|
2809
|
+
onRetry: (error, attempt, delayMs) => {
|
|
2810
|
+
lastFailure = { error };
|
|
2811
|
+
options.warn?.(
|
|
2812
|
+
`etapa "${step.description}": tentativa ${attempt}/${totalRetries + 1} falhou: ${truncate(errorReason(error), 500)} \u2014 nova tentativa em ${delayMs}ms`
|
|
2813
|
+
);
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
);
|
|
2817
|
+
this.noteAttempts(step, attempts);
|
|
2818
|
+
if (lastFailure && step.status === "ok") {
|
|
2819
|
+
step._setStatus("warning", `tentativa ${attempts - 1}/${totalRetries + 1} falhou: ${errorReason(lastFailure.error)}`);
|
|
2820
|
+
}
|
|
2821
|
+
step._close();
|
|
2822
|
+
return result;
|
|
2823
|
+
} catch (error) {
|
|
2824
|
+
this.noteAttempts(step, attempts);
|
|
2825
|
+
if (isTerminalError(error)) step._setStatus("cancelled")._close();
|
|
2826
|
+
else step._failed(error);
|
|
2827
|
+
throw error;
|
|
2828
|
+
}
|
|
2566
2829
|
}
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
return this;
|
|
2830
|
+
noteAttempts(step, attempts) {
|
|
2831
|
+
if (attempts > 1) step._setData("attempts", attempts);
|
|
2570
2832
|
}
|
|
2833
|
+
// ── Metadados ─────────────────────────────────────────────────────────────
|
|
2571
2834
|
setStatus(status) {
|
|
2572
2835
|
this.status = status;
|
|
2573
2836
|
return this;
|
|
2574
2837
|
}
|
|
2575
|
-
|
|
2576
|
-
this.name = name;
|
|
2577
|
-
return this;
|
|
2578
|
-
}
|
|
2838
|
+
// ── Encerramento ──────────────────────────────────────────────────────────
|
|
2579
2839
|
/**
|
|
2580
|
-
* Envia já as
|
|
2840
|
+
* Envia já as etapas raiz finalizadas (e todas as descendentes, fechando as que
|
|
2581
2841
|
* ficaram abertas) como transactions parciais da mesma run (`op` hobots.run.flush,
|
|
2582
|
-
* `event_id` novo, até 200
|
|
2842
|
+
* `event_id` novo, até 200 etapas cada) e as tira da memória. Etapas raiz ainda abertas
|
|
2583
2843
|
* ficam para o próximo flush ou para o `finish()`. É assim que um agente sempre
|
|
2584
2844
|
* ligado (`start()`), cuja run não termina, entrega as etapas enquanto processa.
|
|
2585
|
-
* Devolve quantas
|
|
2845
|
+
* Devolve quantas etapas foram enviadas (0 sem client, sem nada terminado ou depois do `finish()`).
|
|
2586
2846
|
*/
|
|
2587
2847
|
flush() {
|
|
2588
|
-
if (this.finished) return 0;
|
|
2848
|
+
if (!this.client || this.finished) return 0;
|
|
2589
2849
|
const ready = /* @__PURE__ */ new Set();
|
|
2590
2850
|
for (const step of this.steps) {
|
|
2591
2851
|
if (step.parentStepId === this.stepId && step.endTimestamp) ready.add(step.stepId);
|
|
@@ -2601,15 +2861,13 @@ var Transaction = class {
|
|
|
2601
2861
|
remaining.push(step);
|
|
2602
2862
|
}
|
|
2603
2863
|
}
|
|
2604
|
-
for (const step of batch) step.
|
|
2864
|
+
for (const step of batch) step._close();
|
|
2605
2865
|
this.steps.splice(0, this.steps.length, ...remaining);
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
this.client.sendTransaction(this.payload(uuid4(), FLUSH_TRANSACTION_OP, status, startTimestamp, nowISO(), chunk));
|
|
2612
|
-
}
|
|
2866
|
+
for (let i = 0; i < batch.length; i += MAX_STEPS) {
|
|
2867
|
+
const chunk = batch.slice(i, i + MAX_STEPS);
|
|
2868
|
+
const startTimestamp = chunk.reduce((min, step) => step.startTimestamp < min ? step.startTimestamp : min, chunk[0].startTimestamp);
|
|
2869
|
+
const status = chunk.some((step) => step.status === "error") ? "error" : "ok";
|
|
2870
|
+
this.client.sendTransaction(this.payload(uuid4(), FLUSH_TRANSACTION_OP, status, startTimestamp, nowISO(), chunk));
|
|
2613
2871
|
}
|
|
2614
2872
|
return batch.length;
|
|
2615
2873
|
}
|
|
@@ -2626,14 +2884,15 @@ var Transaction = class {
|
|
|
2626
2884
|
start_timestamp: startTimestamp,
|
|
2627
2885
|
timestamp,
|
|
2628
2886
|
steps: steps.map((step) => step.toJSON()),
|
|
2629
|
-
|
|
2887
|
+
// sem tags próprias: o client mescla as do processo
|
|
2888
|
+
tags: void 0,
|
|
2630
2889
|
run_id: this.runId
|
|
2631
2890
|
};
|
|
2632
2891
|
}
|
|
2633
2892
|
finish() {
|
|
2634
2893
|
if (this.finished) return;
|
|
2635
2894
|
this.finished = true;
|
|
2636
|
-
for (const step of this.steps) step.
|
|
2895
|
+
for (const step of this.steps) step._close();
|
|
2637
2896
|
const endTimestamp = nowISO();
|
|
2638
2897
|
this.emit(
|
|
2639
2898
|
"transaction",
|
|
@@ -2661,7 +2920,7 @@ var DEFAULT_RETRY_DELAY_MS = 5e3;
|
|
|
2661
2920
|
async function executeRun(input) {
|
|
2662
2921
|
const { run, handler, api, client, logger, tracker } = input;
|
|
2663
2922
|
const options = handler.options;
|
|
2664
|
-
const tx = new Transaction(client, "run", "hobots.run", run.id);
|
|
2923
|
+
const tx = new Transaction(client, "run", "hobots.run", run.id, { echo: !client });
|
|
2665
2924
|
const reporter = new RunReporter(
|
|
2666
2925
|
api,
|
|
2667
2926
|
run.id,
|
|
@@ -2695,6 +2954,7 @@ async function executeRun(input) {
|
|
|
2695
2954
|
await reporter.finalizeItems();
|
|
2696
2955
|
tx.setStatus("error");
|
|
2697
2956
|
tx.finish();
|
|
2957
|
+
captureRunFailure(client, error, reporter);
|
|
2698
2958
|
reporter.reportFailure(toRunError(error));
|
|
2699
2959
|
logger.debug(`run ${run.id} falhou: ${String(error)}`);
|
|
2700
2960
|
}
|
|
@@ -2704,6 +2964,11 @@ async function executeRun(input) {
|
|
|
2704
2964
|
tracker.remove(reporter);
|
|
2705
2965
|
}
|
|
2706
2966
|
}
|
|
2967
|
+
function captureRunFailure(client, error, reporter) {
|
|
2968
|
+
if (!client?.options.captureRunErrors) return;
|
|
2969
|
+
if (reporter.isDead || isCancellation(error) || wasCaptured(error)) return;
|
|
2970
|
+
client.captureException(error, { type: "error", tags: { mechanism: "run_handler", run_id: reporter.runId } });
|
|
2971
|
+
}
|
|
2707
2972
|
function warnIncompleteProgress(reporter) {
|
|
2708
2973
|
const snapshot = reporter.context.progress.snapshot;
|
|
2709
2974
|
if (!snapshot || snapshot.current >= snapshot.total) return;
|
|
@@ -2895,6 +3160,96 @@ var TaskAgent = class {
|
|
|
2895
3160
|
}
|
|
2896
3161
|
};
|
|
2897
3162
|
|
|
3163
|
+
// src/tasks/single-run.ts
|
|
3164
|
+
var SingleRunRunner = class {
|
|
3165
|
+
constructor(handler, deps) {
|
|
3166
|
+
this.handler = handler;
|
|
3167
|
+
this.deps = deps;
|
|
3168
|
+
this.done = new Promise((resolve2) => {
|
|
3169
|
+
this.resolveDone = resolve2;
|
|
3170
|
+
});
|
|
3171
|
+
}
|
|
3172
|
+
handler;
|
|
3173
|
+
deps;
|
|
3174
|
+
abort = new AbortController();
|
|
3175
|
+
/** Resolve quando a run terminou (ou não pôde começar, ou o runner foi parado antes). */
|
|
3176
|
+
done;
|
|
3177
|
+
resolveDone;
|
|
3178
|
+
async run() {
|
|
3179
|
+
try {
|
|
3180
|
+
const run = await this.openRun();
|
|
3181
|
+
if (!run) return;
|
|
3182
|
+
this.deps.logger.debug(this.opened(run));
|
|
3183
|
+
await executeRun({
|
|
3184
|
+
run,
|
|
3185
|
+
handler: this.handler,
|
|
3186
|
+
api: this.deps.api,
|
|
3187
|
+
client: this.deps.client,
|
|
3188
|
+
logger: this.deps.logger,
|
|
3189
|
+
tracker: this.deps.tracker
|
|
3190
|
+
});
|
|
3191
|
+
} catch (error) {
|
|
3192
|
+
this.deps.logger.error("erro inesperado na execu\xE7\xE3o do agente:", error);
|
|
3193
|
+
} finally {
|
|
3194
|
+
this.resolveDone();
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
/** Desiste de obter a run (close() antes dela existir). Uma run já em execução segue pelo tracker. */
|
|
3198
|
+
stop() {
|
|
3199
|
+
this.abort.abort();
|
|
3200
|
+
}
|
|
3201
|
+
};
|
|
3202
|
+
|
|
3203
|
+
// src/tasks/attached.ts
|
|
3204
|
+
var START_RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
|
|
3205
|
+
function isTransient(error) {
|
|
3206
|
+
if (!(error instanceof TasksApiError)) return true;
|
|
3207
|
+
return error.status >= 500 || error.status === 429;
|
|
3208
|
+
}
|
|
3209
|
+
function describeRejection(runId, error) {
|
|
3210
|
+
if (error instanceof TasksApiError && error.status === 404) {
|
|
3211
|
+
return `run ${runId} n\xE3o existe na plataforma (404) \u2014 nada a executar`;
|
|
3212
|
+
}
|
|
3213
|
+
if (error instanceof TasksApiError && error.status === 409) {
|
|
3214
|
+
return `run ${runId} n\xE3o pode ser iniciada (409: cancelada, j\xE1 iniciada ou de outra inst\xE2ncia) \u2014 nada a executar`;
|
|
3215
|
+
}
|
|
3216
|
+
return `n\xE3o foi poss\xEDvel iniciar a run ${runId} (${String(error)}) \u2014 nada a executar`;
|
|
3217
|
+
}
|
|
3218
|
+
var AttachedRunner = class extends SingleRunRunner {
|
|
3219
|
+
constructor(runId, handler, deps) {
|
|
3220
|
+
super(handler, deps);
|
|
3221
|
+
this.runId = runId;
|
|
3222
|
+
}
|
|
3223
|
+
runId;
|
|
3224
|
+
opened(run) {
|
|
3225
|
+
return `run ${run.id} iniciada (HOBOTS_RUN_ID)`;
|
|
3226
|
+
}
|
|
3227
|
+
async openRun() {
|
|
3228
|
+
for (let attempt = 0; ; attempt++) {
|
|
3229
|
+
if (this.abort.signal.aborted) return void 0;
|
|
3230
|
+
try {
|
|
3231
|
+
return await this.deps.api.startRun(this.runId, { instance: this.deps.instanceInfo() }, this.abort.signal);
|
|
3232
|
+
} catch (error) {
|
|
3233
|
+
if (this.abort.signal.aborted) return void 0;
|
|
3234
|
+
if (!isTransient(error)) {
|
|
3235
|
+
this.deps.logger.error(describeRejection(this.runId, error));
|
|
3236
|
+
return void 0;
|
|
3237
|
+
}
|
|
3238
|
+
const delay = START_RETRY_DELAYS_MS[attempt];
|
|
3239
|
+
if (delay === void 0) {
|
|
3240
|
+
this.deps.logger.error(
|
|
3241
|
+
`n\xE3o foi poss\xEDvel iniciar a run ${this.runId} ap\xF3s ${attempt + 1} tentativas (${String(error)}) \u2014 desistindo`
|
|
3242
|
+
);
|
|
3243
|
+
return void 0;
|
|
3244
|
+
}
|
|
3245
|
+
const wait = jitter(delay);
|
|
3246
|
+
this.deps.logger.warn(`in\xEDcio da run ${this.runId} falhou (${String(error)}), pr\xF3xima tentativa em ${wait}ms`);
|
|
3247
|
+
await abortableSleep(wait, this.abort.signal);
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
};
|
|
3252
|
+
|
|
2898
3253
|
// src/tasks/item-columns.ts
|
|
2899
3254
|
var KEY_RE = /^[a-zA-Z0-9_.-]{1,100}$/;
|
|
2900
3255
|
var MAX_COLUMNS = WIRE_LIMITS.MAX_ITEM_COLUMNS;
|
|
@@ -2932,46 +3287,16 @@ function normalizeItemColumns(input, logger) {
|
|
|
2932
3287
|
}
|
|
2933
3288
|
|
|
2934
3289
|
// src/tasks/local.ts
|
|
2935
|
-
var LocalRunner = class {
|
|
3290
|
+
var LocalRunner = class extends SingleRunRunner {
|
|
3291
|
+
backoff;
|
|
2936
3292
|
constructor(handler, deps) {
|
|
2937
|
-
|
|
2938
|
-
this.deps = deps;
|
|
3293
|
+
super(handler, deps);
|
|
2939
3294
|
this.backoff = new Backoff(deps.logger, "cria\xE7\xE3o da run");
|
|
2940
|
-
this.done = new Promise((resolve2) => {
|
|
2941
|
-
this.resolveDone = resolve2;
|
|
2942
|
-
});
|
|
2943
|
-
}
|
|
2944
|
-
handler;
|
|
2945
|
-
deps;
|
|
2946
|
-
abort = new AbortController();
|
|
2947
|
-
backoff;
|
|
2948
|
-
/** Resolve quando a run terminou (ou o runner foi parado antes de abri-la). */
|
|
2949
|
-
done;
|
|
2950
|
-
resolveDone;
|
|
2951
|
-
async run() {
|
|
2952
|
-
try {
|
|
2953
|
-
const run = await this.createRun();
|
|
2954
|
-
if (!run) return;
|
|
2955
|
-
this.deps.logger.debug(`run ${run.id} aberta (modo start)`);
|
|
2956
|
-
await executeRun({
|
|
2957
|
-
run,
|
|
2958
|
-
handler: this.handler,
|
|
2959
|
-
api: this.deps.api,
|
|
2960
|
-
client: this.deps.client,
|
|
2961
|
-
logger: this.deps.logger,
|
|
2962
|
-
tracker: this.deps.tracker
|
|
2963
|
-
});
|
|
2964
|
-
} catch (error) {
|
|
2965
|
-
this.deps.logger.error("erro inesperado na execu\xE7\xE3o do agente:", error);
|
|
2966
|
-
} finally {
|
|
2967
|
-
this.resolveDone();
|
|
2968
|
-
}
|
|
2969
3295
|
}
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
this.abort.abort();
|
|
3296
|
+
opened(run) {
|
|
3297
|
+
return `run ${run.id} aberta (modo start)`;
|
|
2973
3298
|
}
|
|
2974
|
-
async
|
|
3299
|
+
async openRun() {
|
|
2975
3300
|
for (; ; ) {
|
|
2976
3301
|
if (this.abort.signal.aborted) return void 0;
|
|
2977
3302
|
try {
|
|
@@ -2984,6 +3309,129 @@ var LocalRunner = class {
|
|
|
2984
3309
|
}
|
|
2985
3310
|
};
|
|
2986
3311
|
|
|
3312
|
+
// src/tasks/local-transport.ts
|
|
3313
|
+
var LOCAL_RUN_ID = "local";
|
|
3314
|
+
var ITEM_LINE_PREFIX = "[hobots:item]";
|
|
3315
|
+
var SUMMARY_LINE_PREFIX = "[hobots:summary]";
|
|
3316
|
+
var ATTACHMENTS_DISCARDED_WARNING = "imagens e anexos n\xE3o s\xE3o enviados no modo local (descartados)";
|
|
3317
|
+
function unavailable(name) {
|
|
3318
|
+
return new Error(`arquivo "${name}" enviado pela Central n\xE3o est\xE1 dispon\xEDvel no modo local`);
|
|
3319
|
+
}
|
|
3320
|
+
function cell(value) {
|
|
3321
|
+
return (value ?? "").replace(/\r\n|[\t\r\n]/g, " ");
|
|
3322
|
+
}
|
|
3323
|
+
var LocalRunTransport = class {
|
|
3324
|
+
constructor(params = {}, out = (line) => void process.stdout.write(`${line}
|
|
3325
|
+
`)) {
|
|
3326
|
+
this.params = params;
|
|
3327
|
+
this.out = out;
|
|
3328
|
+
}
|
|
3329
|
+
params;
|
|
3330
|
+
out;
|
|
3331
|
+
counts = { succeeded: 0, occurrence: 0, failed: 0 };
|
|
3332
|
+
warnedAttachments = false;
|
|
3333
|
+
/** Status terminal que o pipeline reportou — `undefined` enquanto a run corre (ou se ela nem reportou). */
|
|
3334
|
+
terminalStatus;
|
|
3335
|
+
async updateRun(_runId, update) {
|
|
3336
|
+
if (update.status === "running") return;
|
|
3337
|
+
this.terminalStatus = update.status;
|
|
3338
|
+
if (update.status === "failed" && update.error && update.error.type !== "RunCanceled") {
|
|
3339
|
+
printRunLog("error", `execu\xE7\xE3o falhou \u2014 ${update.error.type ?? "Error"}: ${update.error.message}`);
|
|
3340
|
+
if (update.error.stack) console.error(update.error.stack);
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
async updateRunOnce() {
|
|
3344
|
+
}
|
|
3345
|
+
async createItems(_runId, request) {
|
|
3346
|
+
for (const item of request.items) {
|
|
3347
|
+
this.counts[item.status]++;
|
|
3348
|
+
this.out(`${ITEM_LINE_PREFIX} ${item.status} ${cell(item.id)} ${cell(item.message)}`);
|
|
3349
|
+
}
|
|
3350
|
+
return { accepted: request.items.length, duplicates: 0 };
|
|
3351
|
+
}
|
|
3352
|
+
/** Totais por status — chame uma vez, no fim da run, antes da linha de conclusão. */
|
|
3353
|
+
printSummary() {
|
|
3354
|
+
const { succeeded, occurrence, failed } = this.counts;
|
|
3355
|
+
this.out(
|
|
3356
|
+
`${SUMMARY_LINE_PREFIX} total=${succeeded + occurrence + failed} ok=${succeeded} avisos=${occurrence} falhas=${failed} pulados=0`
|
|
3357
|
+
);
|
|
3358
|
+
}
|
|
3359
|
+
async createAttachment(_runId, request) {
|
|
3360
|
+
if (!this.warnedAttachments) {
|
|
3361
|
+
this.warnedAttachments = true;
|
|
3362
|
+
printRunLog("warning", ATTACHMENTS_DISCARDED_WARNING);
|
|
3363
|
+
}
|
|
3364
|
+
return { id: request.id, upload_url: "", headers: {}, expires_in: 0 };
|
|
3365
|
+
}
|
|
3366
|
+
async uploadAttachment() {
|
|
3367
|
+
}
|
|
3368
|
+
async confirmAttachment() {
|
|
3369
|
+
}
|
|
3370
|
+
/** Nome do arquivo pelo id (nos params); sem ele, o próprio id. */
|
|
3371
|
+
fileName(fileId) {
|
|
3372
|
+
return collectFileRefs(this.params).find((ref) => ref.id === fileId)?.name ?? fileId;
|
|
3373
|
+
}
|
|
3374
|
+
async requestFileDownload(_runId, fileId) {
|
|
3375
|
+
throw unavailable(this.fileName(fileId));
|
|
3376
|
+
}
|
|
3377
|
+
async fetchFile(url) {
|
|
3378
|
+
throw unavailable(url);
|
|
3379
|
+
}
|
|
3380
|
+
async confirmFileDownload(_runId, fileId) {
|
|
3381
|
+
throw unavailable(this.fileName(fileId));
|
|
3382
|
+
}
|
|
3383
|
+
};
|
|
3384
|
+
|
|
3385
|
+
// src/tasks/local-run.ts
|
|
3386
|
+
var LOCAL_RUN_EXIT_CODES = { succeeded: 0, failed: 1, canceled: 130 };
|
|
3387
|
+
var LOCAL_RUN_CANCEL_GRACE_MS = 4e3;
|
|
3388
|
+
var LOCAL_RUN_BANNER = "modo local \u2014 execu\xE7\xE3o sem a Central; params de HOBOTS_RUN_PARAMS";
|
|
3389
|
+
var LocalRunRunner = class extends SingleRunRunner {
|
|
3390
|
+
constructor(params, handler, deps) {
|
|
3391
|
+
super(handler, deps);
|
|
3392
|
+
this.params = params;
|
|
3393
|
+
}
|
|
3394
|
+
params;
|
|
3395
|
+
canceled = false;
|
|
3396
|
+
startedAt = Date.now();
|
|
3397
|
+
finished = false;
|
|
3398
|
+
run() {
|
|
3399
|
+
this.startedAt = Date.now();
|
|
3400
|
+
return super.run();
|
|
3401
|
+
}
|
|
3402
|
+
opened() {
|
|
3403
|
+
return "run local iniciada (HOBOTS_LOCAL_RUN)";
|
|
3404
|
+
}
|
|
3405
|
+
async openRun() {
|
|
3406
|
+
if (this.abort.signal.aborted) return void 0;
|
|
3407
|
+
return { id: LOCAL_RUN_ID, params: this.params, form: null, source: "local", created_at: nowISO() };
|
|
3408
|
+
}
|
|
3409
|
+
/** SIGINT/SIGTERM: dispara o `ctx.signal` da run em voo — o handler para no próximo ponto de parada. */
|
|
3410
|
+
cancel() {
|
|
3411
|
+
this.canceled = true;
|
|
3412
|
+
this.deps.tracker.singleActive()?.abort();
|
|
3413
|
+
}
|
|
3414
|
+
/** Desfecho da run: cancelada pelo sinal, senão o status terminal reportado (sem reporte = falha). */
|
|
3415
|
+
get outcome() {
|
|
3416
|
+
if (this.canceled) return "canceled";
|
|
3417
|
+
return this.deps.api.terminalStatus ?? "failed";
|
|
3418
|
+
}
|
|
3419
|
+
/**
|
|
3420
|
+
* Fecha o relato no terminal — `[hobots:summary]` e a linha de conclusão — e devolve o
|
|
3421
|
+
* desfecho. Idempotente: o runtime chama ao fim da run ou quando a graça do cancelamento
|
|
3422
|
+
* esgota com o handler ainda rodando.
|
|
3423
|
+
*/
|
|
3424
|
+
finish() {
|
|
3425
|
+
const outcome = this.outcome;
|
|
3426
|
+
if (this.finished) return outcome;
|
|
3427
|
+
this.finished = true;
|
|
3428
|
+
this.deps.api.printSummary();
|
|
3429
|
+
const seconds = Math.round((Date.now() - this.startedAt) / 1e3);
|
|
3430
|
+
printRunLog("info", `execu\xE7\xE3o local conclu\xEDda (${outcome}) em ${seconds}s`);
|
|
3431
|
+
return outcome;
|
|
3432
|
+
}
|
|
3433
|
+
};
|
|
3434
|
+
|
|
2987
3435
|
// src/tasks/registry.ts
|
|
2988
3436
|
var RunTracker = class {
|
|
2989
3437
|
constructor(api) {
|
|
@@ -3099,19 +3547,10 @@ function hostname() {
|
|
|
3099
3547
|
if (!isNode()) return void 0;
|
|
3100
3548
|
return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
|
|
3101
3549
|
}
|
|
3102
|
-
function
|
|
3103
|
-
|
|
3104
|
-
const logger = makeLogger(options.debug ?? false);
|
|
3105
|
-
const environment = resolveEnvironmentOption(options.environment);
|
|
3106
|
-
const client = new Client(
|
|
3107
|
-
{ environment, release: options.release, tags: options.tags, debug: options.debug },
|
|
3108
|
-
config
|
|
3109
|
-
);
|
|
3110
|
-
const api = new TasksApi({ baseUrl: config.runsBaseUrl, key: config.key, logger });
|
|
3111
|
-
const rt = {
|
|
3550
|
+
function baseRuntime(options, handler, logger, api) {
|
|
3551
|
+
return {
|
|
3112
3552
|
options: { ...options, clientSecret: void 0 },
|
|
3113
|
-
|
|
3114
|
-
client,
|
|
3553
|
+
client: void 0,
|
|
3115
3554
|
logger,
|
|
3116
3555
|
api,
|
|
3117
3556
|
handler,
|
|
@@ -3120,18 +3559,55 @@ function createRuntime(options, handler) {
|
|
|
3120
3559
|
heartbeat: void 0,
|
|
3121
3560
|
agent: void 0,
|
|
3122
3561
|
local: void 0,
|
|
3562
|
+
attached: void 0,
|
|
3563
|
+
localRun: void 0,
|
|
3564
|
+
runId: void 0,
|
|
3123
3565
|
instanceId: resolveInstanceId(options.instanceId),
|
|
3124
|
-
environment,
|
|
3566
|
+
environment: void 0,
|
|
3125
3567
|
pollIntervalMs: void 0,
|
|
3126
3568
|
booted: false,
|
|
3127
3569
|
bootScheduled: false,
|
|
3128
3570
|
bootPromise: void 0,
|
|
3129
|
-
uninstallGlobalHandlers:
|
|
3571
|
+
uninstallGlobalHandlers: void 0,
|
|
3130
3572
|
uninstallShutdown: void 0
|
|
3131
3573
|
};
|
|
3574
|
+
}
|
|
3575
|
+
function createRuntime(options, handler, logger) {
|
|
3576
|
+
const config = resolveConfig({ clientSecret: options.clientSecret });
|
|
3577
|
+
const environment = resolveEnvironmentOption(options.environment);
|
|
3578
|
+
const client = new Client(
|
|
3579
|
+
{
|
|
3580
|
+
environment,
|
|
3581
|
+
release: options.release,
|
|
3582
|
+
tags: options.tags,
|
|
3583
|
+
debug: options.debug,
|
|
3584
|
+
captureRunErrors: options.captureUnhandled !== false
|
|
3585
|
+
},
|
|
3586
|
+
config
|
|
3587
|
+
);
|
|
3588
|
+
const api = new TasksApi({ baseUrl: config.runsBaseUrl, key: config.key, logger });
|
|
3589
|
+
const rt = baseRuntime(options, handler, logger, api);
|
|
3590
|
+
rt.client = client;
|
|
3591
|
+
rt.environment = environment;
|
|
3592
|
+
rt.uninstallGlobalHandlers = options.captureUnhandled !== false ? installGlobalHandlers(client) : void 0;
|
|
3132
3593
|
logger.debug("inicializado", { endpoint: config.ingestEndpoint });
|
|
3133
3594
|
return rt;
|
|
3134
3595
|
}
|
|
3596
|
+
function createLocalRunRuntime(options, handler, logger) {
|
|
3597
|
+
let params;
|
|
3598
|
+
try {
|
|
3599
|
+
params = resolveLocalRunParams();
|
|
3600
|
+
} catch (error) {
|
|
3601
|
+
printRunLog("error", error instanceof Error ? error.message : String(error));
|
|
3602
|
+
process.exit(2);
|
|
3603
|
+
throw error;
|
|
3604
|
+
}
|
|
3605
|
+
const transport = new LocalRunTransport(params);
|
|
3606
|
+
const rt = baseRuntime(options, handler, logger, transport);
|
|
3607
|
+
rt.localRun = new LocalRunRunner(params, handler, { api: transport, client: void 0, logger, tracker: rt.tracker });
|
|
3608
|
+
printRunLog("info", LOCAL_RUN_BANNER);
|
|
3609
|
+
return rt;
|
|
3610
|
+
}
|
|
3135
3611
|
function validateHandler(handler, fn) {
|
|
3136
3612
|
if (typeof handler !== "function") {
|
|
3137
3613
|
throw new Error(`[hobots] ${fn}(): o handler precisa ser uma fun\xE7\xE3o \u2014 ${fn}(async (params) => { ... })`);
|
|
@@ -3174,9 +3650,12 @@ function instanceInfo(rt) {
|
|
|
3174
3650
|
machine: rt.sampler?.sample()
|
|
3175
3651
|
};
|
|
3176
3652
|
}
|
|
3653
|
+
function platformApi(rt) {
|
|
3654
|
+
return rt.api;
|
|
3655
|
+
}
|
|
3177
3656
|
function ensureAgent(rt) {
|
|
3178
3657
|
rt.agent ??= new TaskAgent({
|
|
3179
|
-
api: rt
|
|
3658
|
+
api: platformApi(rt),
|
|
3180
3659
|
client: rt.client,
|
|
3181
3660
|
logger: rt.logger,
|
|
3182
3661
|
handler: rt.handler,
|
|
@@ -3188,7 +3667,7 @@ function ensureAgent(rt) {
|
|
|
3188
3667
|
}
|
|
3189
3668
|
function launchLocal(rt) {
|
|
3190
3669
|
const runner = new LocalRunner(rt.handler, {
|
|
3191
|
-
api: rt
|
|
3670
|
+
api: platformApi(rt),
|
|
3192
3671
|
client: rt.client,
|
|
3193
3672
|
logger: rt.logger,
|
|
3194
3673
|
tracker: rt.tracker,
|
|
@@ -3197,7 +3676,57 @@ function launchLocal(rt) {
|
|
|
3197
3676
|
rt.local = runner;
|
|
3198
3677
|
void runner.run();
|
|
3199
3678
|
}
|
|
3679
|
+
function launchAttached(rt, runId) {
|
|
3680
|
+
const runner = new AttachedRunner(runId, rt.handler, {
|
|
3681
|
+
api: platformApi(rt),
|
|
3682
|
+
client: rt.client,
|
|
3683
|
+
logger: rt.logger,
|
|
3684
|
+
tracker: rt.tracker,
|
|
3685
|
+
instanceInfo: () => instanceInfo(rt)
|
|
3686
|
+
});
|
|
3687
|
+
rt.attached = runner;
|
|
3688
|
+
void runner.run();
|
|
3689
|
+
void runner.done.then(() => {
|
|
3690
|
+
if (runtime === rt) void close();
|
|
3691
|
+
});
|
|
3692
|
+
}
|
|
3693
|
+
function launchLocalRun(rt, runner) {
|
|
3694
|
+
let finished = false;
|
|
3695
|
+
const finish = () => {
|
|
3696
|
+
if (finished) return;
|
|
3697
|
+
finished = true;
|
|
3698
|
+
rt.uninstallShutdown?.();
|
|
3699
|
+
rt.uninstallShutdown = void 0;
|
|
3700
|
+
const code = LOCAL_RUN_EXIT_CODES[runner.finish()];
|
|
3701
|
+
if (runtime === rt) void close(0);
|
|
3702
|
+
process.stdout.write("", () => process.exit(code));
|
|
3703
|
+
};
|
|
3704
|
+
if (rt.options.installExitHandlers !== false) {
|
|
3705
|
+
let signaled = false;
|
|
3706
|
+
const onSignal = (signal) => {
|
|
3707
|
+
if (externalListenerCount(signal) > 0 || signaled) return;
|
|
3708
|
+
signaled = true;
|
|
3709
|
+
runner.cancel();
|
|
3710
|
+
void Promise.race([runner.done, sleep(LOCAL_RUN_CANCEL_GRACE_MS)]).then(finish);
|
|
3711
|
+
};
|
|
3712
|
+
const onInt = markInternal(() => onSignal("SIGINT"));
|
|
3713
|
+
const onTerm = markInternal(() => onSignal("SIGTERM"));
|
|
3714
|
+
process.on("SIGINT", onInt);
|
|
3715
|
+
process.on("SIGTERM", onTerm);
|
|
3716
|
+
rt.uninstallShutdown = () => {
|
|
3717
|
+
process.off("SIGINT", onInt);
|
|
3718
|
+
process.off("SIGTERM", onTerm);
|
|
3719
|
+
};
|
|
3720
|
+
}
|
|
3721
|
+
void runner.run();
|
|
3722
|
+
void runner.done.then(finish);
|
|
3723
|
+
}
|
|
3200
3724
|
async function boot(rt) {
|
|
3725
|
+
if (rt.localRun) {
|
|
3726
|
+
rt.booted = true;
|
|
3727
|
+
launchLocalRun(rt, rt.localRun);
|
|
3728
|
+
return;
|
|
3729
|
+
}
|
|
3201
3730
|
if (!rt.instanceId) {
|
|
3202
3731
|
const id = await fallbackInstanceId();
|
|
3203
3732
|
if (runtime !== rt) return;
|
|
@@ -3216,8 +3745,9 @@ async function boot(rt) {
|
|
|
3216
3745
|
if (rt.options.installExitHandlers !== false) {
|
|
3217
3746
|
rt.uninstallShutdown = installShutdownHandlers(rt.tracker);
|
|
3218
3747
|
}
|
|
3219
|
-
if (rt.handler.mode === "
|
|
3220
|
-
else
|
|
3748
|
+
if (rt.handler.mode === "local") launchLocal(rt);
|
|
3749
|
+
else if (rt.runId) launchAttached(rt, rt.runId);
|
|
3750
|
+
else ensureAgent(rt).start();
|
|
3221
3751
|
}
|
|
3222
3752
|
function scheduleBoot(rt) {
|
|
3223
3753
|
if (rt.bootScheduled) return;
|
|
@@ -3233,8 +3763,12 @@ function startAgent(fn, mode, handler, options) {
|
|
|
3233
3763
|
}
|
|
3234
3764
|
const logger = makeLogger(options.debug ?? false);
|
|
3235
3765
|
const agentHandler = { mode, handler, options: resolveRunOptions(options, mode, logger) };
|
|
3236
|
-
|
|
3237
|
-
|
|
3766
|
+
loadDotenv();
|
|
3767
|
+
const rt = isLocalRun() ? createLocalRunRuntime(options, agentHandler, logger) : createRuntime(options, agentHandler, logger);
|
|
3768
|
+
if (!rt.localRun && mode === "remote") {
|
|
3769
|
+
if (options.pollIntervalMs !== void 0) rt.pollIntervalMs = options.pollIntervalMs;
|
|
3770
|
+
rt.runId = resolveRunId(options.runId);
|
|
3771
|
+
}
|
|
3238
3772
|
runtime = rt;
|
|
3239
3773
|
scheduleBoot(rt);
|
|
3240
3774
|
}
|
|
@@ -3257,10 +3791,10 @@ function forwardAttachment(rt, attachment, message, type) {
|
|
|
3257
3791
|
const reporter = currentReporter();
|
|
3258
3792
|
if (!reporter) {
|
|
3259
3793
|
rt.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o");
|
|
3260
|
-
return;
|
|
3794
|
+
return void 0;
|
|
3261
3795
|
}
|
|
3262
3796
|
const withMeta = typeof attachment === "object" && !isImageAttachment(attachment) && "image" in attachment ? image(attachment.image, { caption: attachment.caption, contentType: attachment.contentType }) : attachment;
|
|
3263
|
-
reporter.log(toRunLogLevel(type), message, withMeta);
|
|
3797
|
+
return reporter.log(toRunLogLevel(type), message, withMeta);
|
|
3264
3798
|
}
|
|
3265
3799
|
function captureException(error, hint) {
|
|
3266
3800
|
const rt = runtime;
|
|
@@ -3271,11 +3805,19 @@ function captureException(error, hint) {
|
|
|
3271
3805
|
}
|
|
3272
3806
|
return void 0;
|
|
3273
3807
|
}
|
|
3808
|
+
let attachmentId;
|
|
3274
3809
|
if (hint?.attachment) {
|
|
3275
3810
|
const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
3276
|
-
forwardAttachment(rt, hint.attachment, message, hint.type ?? "error");
|
|
3811
|
+
attachmentId = forwardAttachment(rt, hint.attachment, message, hint.type ?? "error");
|
|
3277
3812
|
}
|
|
3278
|
-
|
|
3813
|
+
if (!rt.client) {
|
|
3814
|
+
rt.logger.debug("captureException ignorado no modo local (sem Central)");
|
|
3815
|
+
return void 0;
|
|
3816
|
+
}
|
|
3817
|
+
const runId = currentReporter()?.runId;
|
|
3818
|
+
const withRun = runId ? { ...hint, tags: { run_id: runId, ...hint?.tags } } : hint;
|
|
3819
|
+
const contexts = runId && attachmentId ? { run: { id: runId, attachments: [attachmentId] } } : void 0;
|
|
3820
|
+
return rt.client.captureException(error, withRun, contexts);
|
|
3279
3821
|
}
|
|
3280
3822
|
async function close(timeoutMs = 3e4) {
|
|
3281
3823
|
const rt = runtime;
|
|
@@ -3284,6 +3826,8 @@ async function close(timeoutMs = 3e4) {
|
|
|
3284
3826
|
await rt.bootPromise;
|
|
3285
3827
|
if (rt.agent) await rt.agent.stop(0);
|
|
3286
3828
|
rt.local?.stop();
|
|
3829
|
+
rt.attached?.stop();
|
|
3830
|
+
rt.localRun?.stop();
|
|
3287
3831
|
const idle = await rt.tracker.waitIdle(timeoutMs);
|
|
3288
3832
|
if (!idle) {
|
|
3289
3833
|
rt.logger.warn(`${rt.tracker.activeCount} run(s) ainda em execu\xE7\xE3o ap\xF3s ${timeoutMs}ms \u2014 abandonando a espera`);
|
|
@@ -3292,7 +3836,7 @@ async function close(timeoutMs = 3e4) {
|
|
|
3292
3836
|
rt.heartbeat?.stop();
|
|
3293
3837
|
rt.uninstallShutdown?.();
|
|
3294
3838
|
rt.uninstallGlobalHandlers?.();
|
|
3295
|
-
const flushed = await rt.client.close(timeoutMs);
|
|
3839
|
+
const flushed = rt.client ? await rt.client.close(timeoutMs) : true;
|
|
3296
3840
|
return idle && flushed;
|
|
3297
3841
|
}
|
|
3298
3842
|
|
|
@@ -3321,11 +3865,13 @@ var noop = () => void 0;
|
|
|
3321
3865
|
var fallbackEachSink = {
|
|
3322
3866
|
report: noop,
|
|
3323
3867
|
startStep: () => void 0,
|
|
3868
|
+
stepScope: (_step, fn) => fn(),
|
|
3324
3869
|
declareTotal: noop,
|
|
3325
3870
|
advance: noop,
|
|
3326
3871
|
checkCanceled: noop,
|
|
3327
3872
|
isAborted: () => false,
|
|
3328
|
-
logError: (message) => printRunLog("error", message)
|
|
3873
|
+
logError: (message) => printRunLog("error", message),
|
|
3874
|
+
flush: () => Promise.resolve()
|
|
3329
3875
|
};
|
|
3330
3876
|
var fallbackItems = {
|
|
3331
3877
|
add: noop,
|
|
@@ -3344,7 +3890,6 @@ var fallbackFiles = {
|
|
|
3344
3890
|
read: outsideRunFiles
|
|
3345
3891
|
};
|
|
3346
3892
|
var neverAborts = new AbortController().signal;
|
|
3347
|
-
var detachedTransaction;
|
|
3348
3893
|
function resolve(member) {
|
|
3349
3894
|
const reporter = currentReporter();
|
|
3350
3895
|
if (reporter) return reporter.context;
|
|
@@ -3365,8 +3910,12 @@ var hobots = Object.defineProperties({}, {
|
|
|
3365
3910
|
items: getter("items", () => fallbackItems),
|
|
3366
3911
|
progress: getter("progress", () => fallbackProgress),
|
|
3367
3912
|
files: getter("files", () => fallbackFiles),
|
|
3368
|
-
transaction: getter("transaction", () => detachedTransaction ??= new Transaction(void 0, "hobots.detached")),
|
|
3369
3913
|
signal: getter("signal", () => neverAborts),
|
|
3914
|
+
step: method((name, fn, options) => {
|
|
3915
|
+
const ctx = resolve("step");
|
|
3916
|
+
if (ctx) return ctx.step(name, fn, options);
|
|
3917
|
+
return new Transaction(void 0, "hobots.detached").step(name, fn, options);
|
|
3918
|
+
}),
|
|
3370
3919
|
throwIfCanceled: method(() => resolve("throwIfCanceled")?.throwIfCanceled()),
|
|
3371
3920
|
sleep: method((ms) => resolve("sleep")?.sleep(ms) ?? sleep(ms)),
|
|
3372
3921
|
flush: method((timeoutMs) => resolve("flush")?.flush(timeoutMs) ?? Promise.resolve()),
|
|
@@ -3401,6 +3950,7 @@ var image2 = hobots.image;
|
|
|
3401
3950
|
var isFileRef2 = hobots.isFileRef;
|
|
3402
3951
|
|
|
3403
3952
|
// src/index.ts
|
|
3953
|
+
loadDotenv();
|
|
3404
3954
|
var src_default = hobots;
|
|
3405
3955
|
export {
|
|
3406
3956
|
captureException2 as captureException,
|