hobots 4.2.0 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -5
- package/dist/index.cjs +331 -103
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -21
- package/dist/index.d.ts +58 -21
- package/dist/index.js +331 -103
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +12 -1
- package/dist/protocol.d.ts +12 -1
- package/docs/api-reference.md +7 -5
- package/docs/concepts.md +3 -3
- package/docs/getting-started.md +4 -2
- package/docs/protocol.md +11 -3
- package/docs/runs.md +11 -11
- package/package.json +1 -1
package/dist/index.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 ? "4.
|
|
7
|
+
var SDK_VERSION = true ? "4.5.0" : "0.0.0-dev";
|
|
61
8
|
function isNode() {
|
|
62
9
|
return typeof process !== "undefined" && typeof process.versions?.node === "string";
|
|
63
10
|
}
|
|
@@ -205,6 +152,104 @@ function colorize(text, color) {
|
|
|
205
152
|
return `${ANSI[color]}${text}${ANSI.reset}`;
|
|
206
153
|
}
|
|
207
154
|
|
|
155
|
+
// src/env.ts
|
|
156
|
+
var INSTANCE_ID_ENV = "HOBOTS_INSTANCE_ID";
|
|
157
|
+
var ENVIRONMENT_ENV = "HOBOTS_ENVIRONMENT";
|
|
158
|
+
var RUN_ID_ENV = "HOBOTS_RUN_ID";
|
|
159
|
+
var dotenvLoaded = false;
|
|
160
|
+
function loadDotenv() {
|
|
161
|
+
if (dotenvLoaded) return;
|
|
162
|
+
dotenvLoaded = true;
|
|
163
|
+
if (!isNode() || typeof process.loadEnvFile !== "function") return;
|
|
164
|
+
try {
|
|
165
|
+
process.loadEnvFile(".env");
|
|
166
|
+
} catch (err) {
|
|
167
|
+
const code = err?.code;
|
|
168
|
+
if (code === "ENOENT") return;
|
|
169
|
+
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`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function resolveInstanceId(option) {
|
|
173
|
+
const fromOption = option?.trim();
|
|
174
|
+
if (fromOption) return fromOption;
|
|
175
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
176
|
+
return process.env[INSTANCE_ID_ENV]?.trim() || void 0;
|
|
177
|
+
}
|
|
178
|
+
function resolveRunId(option) {
|
|
179
|
+
const fromOption = option?.trim();
|
|
180
|
+
if (fromOption) return fromOption;
|
|
181
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
182
|
+
return process.env[RUN_ID_ENV]?.trim() || void 0;
|
|
183
|
+
}
|
|
184
|
+
function resolveEnvironmentOption(option) {
|
|
185
|
+
const fromOption = option?.trim();
|
|
186
|
+
if (fromOption) return fromOption;
|
|
187
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
188
|
+
return process.env[ENVIRONMENT_ENV]?.trim() || void 0;
|
|
189
|
+
}
|
|
190
|
+
async function fallbackInstanceId() {
|
|
191
|
+
if (!isNode()) return "browser";
|
|
192
|
+
try {
|
|
193
|
+
const os = await import("os");
|
|
194
|
+
return os.hostname() || "unknown-host";
|
|
195
|
+
} catch {
|
|
196
|
+
return "unknown-host";
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/stacktrace.ts
|
|
201
|
+
var MAX_FRAMES = 50;
|
|
202
|
+
var MAX_FILENAME_CHARS = 1024;
|
|
203
|
+
var FRAME_RE = /^\s*at\s+(?:async\s+)?(?:(.+?)\s+\()?(.+?)\)?$/;
|
|
204
|
+
var LOCATION_RE = /^(.*?):(\d+):(\d+)$/;
|
|
205
|
+
function isInApp(filename) {
|
|
206
|
+
if (!filename) return false;
|
|
207
|
+
if (filename.startsWith("node:")) return false;
|
|
208
|
+
if (filename.startsWith("internal/")) return false;
|
|
209
|
+
if (filename.includes("node_modules")) return false;
|
|
210
|
+
if (filename === "<anonymous>") return false;
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
function parseStacktrace(stack) {
|
|
214
|
+
if (!stack) return [];
|
|
215
|
+
const frames = [];
|
|
216
|
+
for (const line of stack.split("\n")) {
|
|
217
|
+
if (frames.length >= MAX_FRAMES) break;
|
|
218
|
+
const match = FRAME_RE.exec(line);
|
|
219
|
+
if (!match || !line.trimStart().startsWith("at ")) continue;
|
|
220
|
+
const [, fn, location] = match;
|
|
221
|
+
const loc = LOCATION_RE.exec(location ?? "");
|
|
222
|
+
const filename = loc ? loc[1] : location;
|
|
223
|
+
frames.push({
|
|
224
|
+
function: fn || "<anonymous>",
|
|
225
|
+
filename: filename ? filename.slice(0, MAX_FILENAME_CHARS) : void 0,
|
|
226
|
+
lineno: loc?.[2] ? Number(loc[2]) : void 0,
|
|
227
|
+
colno: loc?.[3] ? Number(loc[3]) : void 0,
|
|
228
|
+
in_app: isInApp(filename)
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
return frames;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/scrub.ts
|
|
235
|
+
var SECRET_PATTERNS = [
|
|
236
|
+
// client secret da Hobots
|
|
237
|
+
[/hb_[a-f0-9]{16,}/gi, "[REDACTED]"],
|
|
238
|
+
// tokens bearer em headers/mensagens
|
|
239
|
+
[/\b(authorization:?\s*)?bearer\s+[a-z0-9._~+/=-]{8,}/gi, "Bearer [REDACTED]"],
|
|
240
|
+
// chaves no formato sk-… (OpenAI e afins)
|
|
241
|
+
[/\bsk-[a-z0-9_-]{16,}\b/gi, "[REDACTED]"],
|
|
242
|
+
// credenciais embutidas em URL (https://user:pass@host)
|
|
243
|
+
[/\/\/[^/\s:@]+:[^/\s@]+@/g, "//[REDACTED]@"]
|
|
244
|
+
];
|
|
245
|
+
function scrubSecrets(text) {
|
|
246
|
+
let out = text;
|
|
247
|
+
for (const [pattern, replacement] of SECRET_PATTERNS) {
|
|
248
|
+
out = out.replace(pattern, replacement);
|
|
249
|
+
}
|
|
250
|
+
return out;
|
|
251
|
+
}
|
|
252
|
+
|
|
208
253
|
// src/eventbuilder.ts
|
|
209
254
|
var MAX_EXCEPTION_VALUE_CHARS = 8192;
|
|
210
255
|
function eventFromException(error, type = "error") {
|
|
@@ -356,6 +401,10 @@ function clipFingerprint(fingerprint) {
|
|
|
356
401
|
if (fingerprint === void 0) return void 0;
|
|
357
402
|
return fingerprint.slice(0, MAX_FINGERPRINT_ITEMS).map((item) => truncate(String(item), MAX_FINGERPRINT_ITEM_CHARS));
|
|
358
403
|
}
|
|
404
|
+
var captured = /* @__PURE__ */ new WeakSet();
|
|
405
|
+
function wasCaptured(error) {
|
|
406
|
+
return typeof error === "object" && error !== null && captured.has(error);
|
|
407
|
+
}
|
|
359
408
|
function runtimeContexts() {
|
|
360
409
|
if (isNode()) {
|
|
361
410
|
return {
|
|
@@ -407,6 +456,7 @@ var Client = class {
|
|
|
407
456
|
return `Client { baseUrl: '${this.#config.baseUrl}' }`;
|
|
408
457
|
}
|
|
409
458
|
captureException(error, hint) {
|
|
459
|
+
if (typeof error === "object" && error !== null) captured.add(error);
|
|
410
460
|
return this.captureEvent(eventFromException(error, hint?.type ?? "error"), hint);
|
|
411
461
|
}
|
|
412
462
|
captureEvent(event, hint) {
|
|
@@ -514,12 +564,12 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
514
564
|
const origin = fromOption ? "clientSecret" : `env ${CLIENT_SECRET_ENV}`;
|
|
515
565
|
if (!key) {
|
|
516
566
|
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
|
|
567
|
+
`${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
568
|
);
|
|
519
569
|
}
|
|
520
570
|
if (!SECRET_PATTERN.test(key)) {
|
|
521
571
|
throw new Error(
|
|
522
|
-
`${prefix} ${origin} com formato inv\xE1lido (${key.length} caracteres) \u2014 esperado "hb_" + 64 hex; copie o Client Secret
|
|
572
|
+
`${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
573
|
);
|
|
524
574
|
}
|
|
525
575
|
const baseUrl = validateBaseUrl(envBaseUrl() ?? DEFAULT_BASE_URL, prefix);
|
|
@@ -531,31 +581,6 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
531
581
|
};
|
|
532
582
|
}
|
|
533
583
|
|
|
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
584
|
// src/heartbeat.ts
|
|
560
585
|
var DEFAULT_INTERVAL_MS = WIRE_LIMITS.HEARTBEAT_DEFAULT_INTERVAL_MS;
|
|
561
586
|
var MIN_REPORTED_INTERVAL_MS = WIRE_LIMITS.HEARTBEAT_MIN_INTERVAL_MS;
|
|
@@ -917,6 +942,42 @@ var TasksApi = class {
|
|
|
917
942
|
created_at: typeof body.created_at === "string" ? body.created_at : (/* @__PURE__ */ new Date()).toISOString()
|
|
918
943
|
};
|
|
919
944
|
}
|
|
945
|
+
/**
|
|
946
|
+
* Anexa o processo a uma run já reivindicada para esta instância (env HOBOTS_RUN_ID):
|
|
947
|
+
* POST /v1/runs/:id/start. Um request só — quem decide o retry é o AttachedRunner.
|
|
948
|
+
* 404 (não existe) e 409 (cancelada, já iniciada ou de outra instância) viram
|
|
949
|
+
* TasksApiError com o status.
|
|
950
|
+
*/
|
|
951
|
+
async startRun(runId, request, signal) {
|
|
952
|
+
const { signal: timeoutSignal, cleanup } = withTimeoutSignal(UPDATE_TIMEOUT_MS, signal);
|
|
953
|
+
let response;
|
|
954
|
+
try {
|
|
955
|
+
response = await fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/start`, {
|
|
956
|
+
redirect: "error",
|
|
957
|
+
method: "POST",
|
|
958
|
+
headers: this.headers(),
|
|
959
|
+
body: JSON.stringify(request),
|
|
960
|
+
signal: timeoutSignal
|
|
961
|
+
});
|
|
962
|
+
} finally {
|
|
963
|
+
cleanup();
|
|
964
|
+
}
|
|
965
|
+
if (!response.ok) {
|
|
966
|
+
throw new TasksApiError(`in\xEDcio da run falhou (HTTP ${response.status})`, response.status);
|
|
967
|
+
}
|
|
968
|
+
const body = await response.json().catch(() => null);
|
|
969
|
+
if (typeof body?.id !== "string") {
|
|
970
|
+
throw new TasksApiError("resposta inv\xE1lida do in\xEDcio da run (id ausente)", 0);
|
|
971
|
+
}
|
|
972
|
+
const form = body.form;
|
|
973
|
+
return {
|
|
974
|
+
id: body.id,
|
|
975
|
+
params: body.params && typeof body.params === "object" && !Array.isArray(body.params) ? body.params : {},
|
|
976
|
+
form: typeof form?.id === "string" && typeof form.name === "string" ? { id: form.id, name: form.name } : null,
|
|
977
|
+
source: typeof body.source === "string" ? body.source : "hobots",
|
|
978
|
+
created_at: typeof body.created_at === "string" ? body.created_at : (/* @__PURE__ */ new Date()).toISOString()
|
|
979
|
+
};
|
|
980
|
+
}
|
|
920
981
|
/** Nunca confia no shape da resposta: runs malformadas são descartadas e o `max` é respeitado. */
|
|
921
982
|
parsePollResponse(body, max) {
|
|
922
983
|
const runs = body?.runs;
|
|
@@ -1174,7 +1235,7 @@ var Backoff = class {
|
|
|
1174
1235
|
this.authFailures++;
|
|
1175
1236
|
if (this.authFailures === 1 || this.authFailures % 10 === 0) {
|
|
1176
1237
|
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
|
|
1238
|
+
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
1239
|
);
|
|
1179
1240
|
}
|
|
1180
1241
|
return Math.max(this.floorMs, AUTH_FAILURE_INTERVAL_MS);
|
|
@@ -1200,6 +1261,9 @@ var TERMINAL_ERROR_NAMES = /* @__PURE__ */ new Set(["RunCanceled", "AbortError",
|
|
|
1200
1261
|
function isTerminalError(error) {
|
|
1201
1262
|
return error instanceof Error && TERMINAL_ERROR_NAMES.has(error.name);
|
|
1202
1263
|
}
|
|
1264
|
+
function isCancellation(error) {
|
|
1265
|
+
return error instanceof Error && (error.name === "RunCanceled" || error.name === "AbortError");
|
|
1266
|
+
}
|
|
1203
1267
|
|
|
1204
1268
|
// src/tasks/each.ts
|
|
1205
1269
|
function thrownMessage(error) {
|
|
@@ -1265,16 +1329,15 @@ async function eachItem(list, handler, options, sink) {
|
|
|
1265
1329
|
if (!handle.reported) handle.succeeded();
|
|
1266
1330
|
} catch (error) {
|
|
1267
1331
|
if (isTerminalError(error) || sink.isAborted()) {
|
|
1268
|
-
step?.setStatus("cancelled");
|
|
1269
|
-
step?.finish();
|
|
1332
|
+
step?.setStatus("cancelled").succeeded();
|
|
1270
1333
|
throw error;
|
|
1271
1334
|
}
|
|
1272
1335
|
const message = thrownMessage(error);
|
|
1273
1336
|
if (!handle.reported) handle.failed(message);
|
|
1274
|
-
step?.
|
|
1337
|
+
step?.failed(error);
|
|
1275
1338
|
sink.logError(`item ${description} falhou: ${message}`);
|
|
1276
1339
|
}
|
|
1277
|
-
step?.
|
|
1340
|
+
step?.succeeded();
|
|
1278
1341
|
if (options.progress !== false) sink.advance();
|
|
1279
1342
|
result.total += 1;
|
|
1280
1343
|
result[handle.status ?? "succeeded"] += 1;
|
|
@@ -1716,7 +1779,7 @@ var RunReporter = class {
|
|
|
1716
1779
|
tx.setStepGuard(checkCanceled);
|
|
1717
1780
|
const eachSink = {
|
|
1718
1781
|
report: (status, message, payload, id, image3) => this.addItem(status, message, payload, id, image3),
|
|
1719
|
-
startStep: (op, description) => tx.
|
|
1782
|
+
startStep: (op, description) => tx.start(op, description),
|
|
1720
1783
|
declareTotal: (total) => {
|
|
1721
1784
|
if (this.progressTotal == null) this.setProgress(this.progressCurrent, total);
|
|
1722
1785
|
},
|
|
@@ -1809,8 +1872,17 @@ var RunReporter = class {
|
|
|
1809
1872
|
itemSeq = 0;
|
|
1810
1873
|
/** Servidor sem o recurso (404) — para de tentar (e de avisar) nesta run. */
|
|
1811
1874
|
itemsUnsupported = false;
|
|
1812
|
-
/**
|
|
1875
|
+
/**
|
|
1876
|
+
* Cota de itens por run do servidor (429, legado — hoje a api não tem teto de itens) —
|
|
1877
|
+
* degrada com um aviso só.
|
|
1878
|
+
*/
|
|
1813
1879
|
itemsQuotaExceeded = false;
|
|
1880
|
+
/**
|
|
1881
|
+
* Armazenamento do plano cheio (402 `storage_limit`): o lote é descartado e o aviso sai
|
|
1882
|
+
* uma vez por episódio. Os envios continuam — num agente `start()` o espaço pode ser
|
|
1883
|
+
* liberado (ou o plano trocado) sem reiniciar; um lote aceito rearma o aviso.
|
|
1884
|
+
*/
|
|
1885
|
+
itemsStorageFull = false;
|
|
1814
1886
|
/**
|
|
1815
1887
|
* Só o kill() do exit path fecha os itens — `dead` (run cancelada/finalizada no
|
|
1816
1888
|
* servidor) NÃO fecha: o servidor aceita itens de run terminal (trabalho já feito).
|
|
@@ -2120,6 +2192,7 @@ var RunReporter = class {
|
|
|
2120
2192
|
if (this.itemsUnsupported) return;
|
|
2121
2193
|
try {
|
|
2122
2194
|
await this.api.createItems(this.runId, { items: batch });
|
|
2195
|
+
this.itemsStorageFull = false;
|
|
2123
2196
|
} catch (error) {
|
|
2124
2197
|
if (error instanceof TasksApiError && error.status === 404) {
|
|
2125
2198
|
this.itemsUnsupported = true;
|
|
@@ -2133,6 +2206,13 @@ var RunReporter = class {
|
|
|
2133
2206
|
}
|
|
2134
2207
|
return;
|
|
2135
2208
|
}
|
|
2209
|
+
if (error instanceof TasksApiError && error.status === 402) {
|
|
2210
|
+
if (!this.itemsStorageFull) {
|
|
2211
|
+
this.itemsStorageFull = true;
|
|
2212
|
+
this.log("warning", "itens descartados: armazenamento do plano cheio \u2014 libere espa\xE7o ou fa\xE7a upgrade em Plano e cobran\xE7a");
|
|
2213
|
+
}
|
|
2214
|
+
return;
|
|
2215
|
+
}
|
|
2136
2216
|
if (error instanceof TasksApiError) {
|
|
2137
2217
|
this.logger.error(`lote de ${batch.length} item(ns) rejeitado pelo servidor:`, error);
|
|
2138
2218
|
this.log("warning", `${batch.length} item(ns) descartado(s): envio rejeitado pelo servidor (HTTP ${error.status})`);
|
|
@@ -2417,16 +2497,19 @@ var Step = class {
|
|
|
2417
2497
|
return this;
|
|
2418
2498
|
}
|
|
2419
2499
|
/**
|
|
2420
|
-
* Marca a step como `error` guardando o motivo em `data.error` ({ type, message })
|
|
2421
|
-
* Aceita qualquer valor lançado; `message` sobrescreve a mensagem do erro.
|
|
2500
|
+
* Marca a step como `error` guardando o motivo em `data.error` ({ type, message }) e a
|
|
2501
|
+
* fecha. Aceita qualquer valor lançado; `message` sobrescreve a mensagem do erro.
|
|
2422
2502
|
* `options.image` pendura um print do estado da tela em `data.attachments`.
|
|
2503
|
+
* Numa step já fechada não faz nada.
|
|
2423
2504
|
*/
|
|
2424
|
-
|
|
2505
|
+
failed(error, message, options) {
|
|
2506
|
+
if (this.endTimestamp) return this;
|
|
2425
2507
|
this.status = "error";
|
|
2426
2508
|
const type = error instanceof Error ? error.name : typeof error === "object" && error !== null ? "Error" : void 0;
|
|
2427
2509
|
const raw = message ?? (error instanceof Error ? error.message || error.name : String(error));
|
|
2428
2510
|
this.setData("error", { ...type ? { type } : {}, message: stepMessage(raw) });
|
|
2429
2511
|
if (options?.image !== void 0) this.attachImage(options.image);
|
|
2512
|
+
this.close();
|
|
2430
2513
|
return this;
|
|
2431
2514
|
}
|
|
2432
2515
|
/**
|
|
@@ -2441,7 +2524,7 @@ var Step = class {
|
|
|
2441
2524
|
if (!id) return;
|
|
2442
2525
|
this.setData("attachments", [...current, id]);
|
|
2443
2526
|
}
|
|
2444
|
-
/** Motivo registrado via setStatus(…, message) ou
|
|
2527
|
+
/** Motivo registrado via setStatus(…, message) ou failed — usado no log de FINISH. */
|
|
2445
2528
|
reason() {
|
|
2446
2529
|
const error = this.data?.error;
|
|
2447
2530
|
const text = typeof error?.message === "string" ? error.message : this.data?.message;
|
|
@@ -2452,10 +2535,18 @@ var Step = class {
|
|
|
2452
2535
|
return this;
|
|
2453
2536
|
}
|
|
2454
2537
|
/** Step aninhada (filha desta step). */
|
|
2455
|
-
|
|
2538
|
+
start(op, description) {
|
|
2456
2539
|
return this.transaction.createStep(this.stepId, op, description);
|
|
2457
2540
|
}
|
|
2458
|
-
|
|
2541
|
+
/**
|
|
2542
|
+
* Fecha a step. O status fica o que estiver (`ok` por padrão, ou o de `setStatus`).
|
|
2543
|
+
* Idempotente; a transaction fecha no fim da run o que ficou aberto.
|
|
2544
|
+
*/
|
|
2545
|
+
succeeded() {
|
|
2546
|
+
this.close();
|
|
2547
|
+
return this;
|
|
2548
|
+
}
|
|
2549
|
+
close() {
|
|
2459
2550
|
if (this.endTimestamp) return;
|
|
2460
2551
|
this.endTimestamp = nowISO();
|
|
2461
2552
|
this.transaction.emit(
|
|
@@ -2548,7 +2639,7 @@ var Transaction = class {
|
|
|
2548
2639
|
}
|
|
2549
2640
|
/**
|
|
2550
2641
|
* Conecta a checagem de cancelamento chamada antes de cada step nova — é assim que
|
|
2551
|
-
* `hobots.transaction.
|
|
2642
|
+
* `hobots.transaction.start()` (e `step.start()`) param uma run cancelada.
|
|
2552
2643
|
*/
|
|
2553
2644
|
setStepGuard(guard) {
|
|
2554
2645
|
this.stepGuard = guard;
|
|
@@ -2560,10 +2651,32 @@ var Transaction = class {
|
|
|
2560
2651
|
this.emit("step", "start", stepLabel(op, description));
|
|
2561
2652
|
return step;
|
|
2562
2653
|
}
|
|
2563
|
-
/** Step direta da transaction. */
|
|
2564
|
-
|
|
2654
|
+
/** Step direta da transaction. `op` é o nome usado por `succeeded(name)`/`failed(error, name)`. */
|
|
2655
|
+
start(op, description) {
|
|
2565
2656
|
return this.createStep(this.stepId, op, description);
|
|
2566
2657
|
}
|
|
2658
|
+
/**
|
|
2659
|
+
* Fecha a step aberta mais recente — com `name`, a mais recente aberta com esse `op`.
|
|
2660
|
+
* Sem step aberta (ou sem nenhuma com esse nome), não faz nada e devolve undefined.
|
|
2661
|
+
*/
|
|
2662
|
+
succeeded(name) {
|
|
2663
|
+
return this.findOpenStep(name)?.succeeded();
|
|
2664
|
+
}
|
|
2665
|
+
/**
|
|
2666
|
+
* Marca como `error` e fecha a step aberta mais recente — com `name`, a mais recente
|
|
2667
|
+
* aberta com esse `op`. `options.message` sobrescreve a mensagem do erro e
|
|
2668
|
+
* `options.image` pendura um print. Sem step aberta, não faz nada e devolve undefined.
|
|
2669
|
+
*/
|
|
2670
|
+
failed(error, name, options) {
|
|
2671
|
+
return this.findOpenStep(name)?.failed(error, options?.message, options);
|
|
2672
|
+
}
|
|
2673
|
+
findOpenStep(name) {
|
|
2674
|
+
for (let i = this.steps.length - 1; i >= 0; i--) {
|
|
2675
|
+
const step = this.steps[i];
|
|
2676
|
+
if (!step.endTimestamp && (name === void 0 || step.op === name)) return step;
|
|
2677
|
+
}
|
|
2678
|
+
return void 0;
|
|
2679
|
+
}
|
|
2567
2680
|
setTag(key, value) {
|
|
2568
2681
|
this.tags[key] = value;
|
|
2569
2682
|
return this;
|
|
@@ -2601,7 +2714,7 @@ var Transaction = class {
|
|
|
2601
2714
|
remaining.push(step);
|
|
2602
2715
|
}
|
|
2603
2716
|
}
|
|
2604
|
-
for (const step of batch) step.
|
|
2717
|
+
for (const step of batch) step.succeeded();
|
|
2605
2718
|
this.steps.splice(0, this.steps.length, ...remaining);
|
|
2606
2719
|
if (this.client) {
|
|
2607
2720
|
for (let i = 0; i < batch.length; i += MAX_STEPS) {
|
|
@@ -2633,7 +2746,7 @@ var Transaction = class {
|
|
|
2633
2746
|
finish() {
|
|
2634
2747
|
if (this.finished) return;
|
|
2635
2748
|
this.finished = true;
|
|
2636
|
-
for (const step of this.steps) step.
|
|
2749
|
+
for (const step of this.steps) step.succeeded();
|
|
2637
2750
|
const endTimestamp = nowISO();
|
|
2638
2751
|
this.emit(
|
|
2639
2752
|
"transaction",
|
|
@@ -2695,6 +2808,7 @@ async function executeRun(input) {
|
|
|
2695
2808
|
await reporter.finalizeItems();
|
|
2696
2809
|
tx.setStatus("error");
|
|
2697
2810
|
tx.finish();
|
|
2811
|
+
captureRunFailure(client, error, reporter);
|
|
2698
2812
|
reporter.reportFailure(toRunError(error));
|
|
2699
2813
|
logger.debug(`run ${run.id} falhou: ${String(error)}`);
|
|
2700
2814
|
}
|
|
@@ -2704,6 +2818,11 @@ async function executeRun(input) {
|
|
|
2704
2818
|
tracker.remove(reporter);
|
|
2705
2819
|
}
|
|
2706
2820
|
}
|
|
2821
|
+
function captureRunFailure(client, error, reporter) {
|
|
2822
|
+
if (!client?.options.captureRunErrors) return;
|
|
2823
|
+
if (reporter.isDead || isCancellation(error) || wasCaptured(error)) return;
|
|
2824
|
+
client.captureException(error, { type: "error", tags: { mechanism: "run_handler", run_id: reporter.runId } });
|
|
2825
|
+
}
|
|
2707
2826
|
function warnIncompleteProgress(reporter) {
|
|
2708
2827
|
const snapshot = reporter.context.progress.snapshot;
|
|
2709
2828
|
if (!snapshot || snapshot.current >= snapshot.total) return;
|
|
@@ -2895,6 +3014,86 @@ var TaskAgent = class {
|
|
|
2895
3014
|
}
|
|
2896
3015
|
};
|
|
2897
3016
|
|
|
3017
|
+
// src/tasks/attached.ts
|
|
3018
|
+
var START_RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
|
|
3019
|
+
function isTransient(error) {
|
|
3020
|
+
if (!(error instanceof TasksApiError)) return true;
|
|
3021
|
+
return error.status >= 500 || error.status === 429;
|
|
3022
|
+
}
|
|
3023
|
+
function describeRejection(runId, error) {
|
|
3024
|
+
if (error instanceof TasksApiError && error.status === 404) {
|
|
3025
|
+
return `run ${runId} n\xE3o existe na plataforma (404) \u2014 nada a executar`;
|
|
3026
|
+
}
|
|
3027
|
+
if (error instanceof TasksApiError && error.status === 409) {
|
|
3028
|
+
return `run ${runId} n\xE3o pode ser iniciada (409: cancelada, j\xE1 iniciada ou de outra inst\xE2ncia) \u2014 nada a executar`;
|
|
3029
|
+
}
|
|
3030
|
+
return `n\xE3o foi poss\xEDvel iniciar a run ${runId} (${String(error)}) \u2014 nada a executar`;
|
|
3031
|
+
}
|
|
3032
|
+
var AttachedRunner = class {
|
|
3033
|
+
constructor(runId, handler, deps) {
|
|
3034
|
+
this.runId = runId;
|
|
3035
|
+
this.handler = handler;
|
|
3036
|
+
this.deps = deps;
|
|
3037
|
+
this.done = new Promise((resolve2) => {
|
|
3038
|
+
this.resolveDone = resolve2;
|
|
3039
|
+
});
|
|
3040
|
+
}
|
|
3041
|
+
runId;
|
|
3042
|
+
handler;
|
|
3043
|
+
deps;
|
|
3044
|
+
abort = new AbortController();
|
|
3045
|
+
/** Resolve quando a run terminou (ou não pôde ser iniciada, ou o runner foi parado antes). */
|
|
3046
|
+
done;
|
|
3047
|
+
resolveDone;
|
|
3048
|
+
async run() {
|
|
3049
|
+
try {
|
|
3050
|
+
const run = await this.startRun();
|
|
3051
|
+
if (!run) return;
|
|
3052
|
+
this.deps.logger.debug(`run ${run.id} iniciada (HOBOTS_RUN_ID)`);
|
|
3053
|
+
await executeRun({
|
|
3054
|
+
run,
|
|
3055
|
+
handler: this.handler,
|
|
3056
|
+
api: this.deps.api,
|
|
3057
|
+
client: this.deps.client,
|
|
3058
|
+
logger: this.deps.logger,
|
|
3059
|
+
tracker: this.deps.tracker
|
|
3060
|
+
});
|
|
3061
|
+
} catch (error) {
|
|
3062
|
+
this.deps.logger.error("erro inesperado na execu\xE7\xE3o do agente:", error);
|
|
3063
|
+
} finally {
|
|
3064
|
+
this.resolveDone();
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
/** Desiste de iniciar a run (close() antes da plataforma responder). Uma run já iniciada segue pelo tracker. */
|
|
3068
|
+
stop() {
|
|
3069
|
+
this.abort.abort();
|
|
3070
|
+
}
|
|
3071
|
+
async startRun() {
|
|
3072
|
+
for (let attempt = 0; ; attempt++) {
|
|
3073
|
+
if (this.abort.signal.aborted) return void 0;
|
|
3074
|
+
try {
|
|
3075
|
+
return await this.deps.api.startRun(this.runId, { instance: this.deps.instanceInfo() }, this.abort.signal);
|
|
3076
|
+
} catch (error) {
|
|
3077
|
+
if (this.abort.signal.aborted) return void 0;
|
|
3078
|
+
if (!isTransient(error)) {
|
|
3079
|
+
this.deps.logger.error(describeRejection(this.runId, error));
|
|
3080
|
+
return void 0;
|
|
3081
|
+
}
|
|
3082
|
+
const delay = START_RETRY_DELAYS_MS[attempt];
|
|
3083
|
+
if (delay === void 0) {
|
|
3084
|
+
this.deps.logger.error(
|
|
3085
|
+
`n\xE3o foi poss\xEDvel iniciar a run ${this.runId} ap\xF3s ${attempt + 1} tentativas (${String(error)}) \u2014 desistindo`
|
|
3086
|
+
);
|
|
3087
|
+
return void 0;
|
|
3088
|
+
}
|
|
3089
|
+
const wait = jitter(delay);
|
|
3090
|
+
this.deps.logger.warn(`in\xEDcio da run ${this.runId} falhou (${String(error)}), pr\xF3xima tentativa em ${wait}ms`);
|
|
3091
|
+
await abortableSleep(wait, this.abort.signal);
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
};
|
|
3096
|
+
|
|
2898
3097
|
// src/tasks/item-columns.ts
|
|
2899
3098
|
var KEY_RE = /^[a-zA-Z0-9_.-]{1,100}$/;
|
|
2900
3099
|
var MAX_COLUMNS = WIRE_LIMITS.MAX_ITEM_COLUMNS;
|
|
@@ -3100,11 +3299,18 @@ function hostname() {
|
|
|
3100
3299
|
return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
|
|
3101
3300
|
}
|
|
3102
3301
|
function createRuntime(options, handler) {
|
|
3302
|
+
loadDotenv();
|
|
3103
3303
|
const config = resolveConfig({ clientSecret: options.clientSecret });
|
|
3104
3304
|
const logger = makeLogger(options.debug ?? false);
|
|
3105
3305
|
const environment = resolveEnvironmentOption(options.environment);
|
|
3106
3306
|
const client = new Client(
|
|
3107
|
-
{
|
|
3307
|
+
{
|
|
3308
|
+
environment,
|
|
3309
|
+
release: options.release,
|
|
3310
|
+
tags: options.tags,
|
|
3311
|
+
debug: options.debug,
|
|
3312
|
+
captureRunErrors: options.captureUnhandled !== false
|
|
3313
|
+
},
|
|
3108
3314
|
config
|
|
3109
3315
|
);
|
|
3110
3316
|
const api = new TasksApi({ baseUrl: config.runsBaseUrl, key: config.key, logger });
|
|
@@ -3120,6 +3326,8 @@ function createRuntime(options, handler) {
|
|
|
3120
3326
|
heartbeat: void 0,
|
|
3121
3327
|
agent: void 0,
|
|
3122
3328
|
local: void 0,
|
|
3329
|
+
attached: void 0,
|
|
3330
|
+
runId: void 0,
|
|
3123
3331
|
instanceId: resolveInstanceId(options.instanceId),
|
|
3124
3332
|
environment,
|
|
3125
3333
|
pollIntervalMs: void 0,
|
|
@@ -3197,6 +3405,20 @@ function launchLocal(rt) {
|
|
|
3197
3405
|
rt.local = runner;
|
|
3198
3406
|
void runner.run();
|
|
3199
3407
|
}
|
|
3408
|
+
function launchAttached(rt, runId) {
|
|
3409
|
+
const runner = new AttachedRunner(runId, rt.handler, {
|
|
3410
|
+
api: rt.api,
|
|
3411
|
+
client: rt.client,
|
|
3412
|
+
logger: rt.logger,
|
|
3413
|
+
tracker: rt.tracker,
|
|
3414
|
+
instanceInfo: () => instanceInfo(rt)
|
|
3415
|
+
});
|
|
3416
|
+
rt.attached = runner;
|
|
3417
|
+
void runner.run();
|
|
3418
|
+
void runner.done.then(() => {
|
|
3419
|
+
if (runtime === rt) void close();
|
|
3420
|
+
});
|
|
3421
|
+
}
|
|
3200
3422
|
async function boot(rt) {
|
|
3201
3423
|
if (!rt.instanceId) {
|
|
3202
3424
|
const id = await fallbackInstanceId();
|
|
@@ -3216,8 +3438,9 @@ async function boot(rt) {
|
|
|
3216
3438
|
if (rt.options.installExitHandlers !== false) {
|
|
3217
3439
|
rt.uninstallShutdown = installShutdownHandlers(rt.tracker);
|
|
3218
3440
|
}
|
|
3219
|
-
if (rt.handler.mode === "
|
|
3220
|
-
else
|
|
3441
|
+
if (rt.handler.mode === "local") launchLocal(rt);
|
|
3442
|
+
else if (rt.runId) launchAttached(rt, rt.runId);
|
|
3443
|
+
else ensureAgent(rt).start();
|
|
3221
3444
|
}
|
|
3222
3445
|
function scheduleBoot(rt) {
|
|
3223
3446
|
if (rt.bootScheduled) return;
|
|
@@ -3235,6 +3458,7 @@ function startAgent(fn, mode, handler, options) {
|
|
|
3235
3458
|
const agentHandler = { mode, handler, options: resolveRunOptions(options, mode, logger) };
|
|
3236
3459
|
const rt = createRuntime(options, agentHandler);
|
|
3237
3460
|
if (mode === "remote" && options.pollIntervalMs !== void 0) rt.pollIntervalMs = options.pollIntervalMs;
|
|
3461
|
+
if (mode === "remote") rt.runId = resolveRunId(options.runId);
|
|
3238
3462
|
runtime = rt;
|
|
3239
3463
|
scheduleBoot(rt);
|
|
3240
3464
|
}
|
|
@@ -3275,7 +3499,9 @@ function captureException(error, hint) {
|
|
|
3275
3499
|
const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
3276
3500
|
forwardAttachment(rt, hint.attachment, message, hint.type ?? "error");
|
|
3277
3501
|
}
|
|
3278
|
-
|
|
3502
|
+
const runId = currentReporter()?.runId;
|
|
3503
|
+
const withRun = runId ? { ...hint, tags: { run_id: runId, ...hint?.tags } } : hint;
|
|
3504
|
+
return rt.client.captureException(error, withRun);
|
|
3279
3505
|
}
|
|
3280
3506
|
async function close(timeoutMs = 3e4) {
|
|
3281
3507
|
const rt = runtime;
|
|
@@ -3284,6 +3510,7 @@ async function close(timeoutMs = 3e4) {
|
|
|
3284
3510
|
await rt.bootPromise;
|
|
3285
3511
|
if (rt.agent) await rt.agent.stop(0);
|
|
3286
3512
|
rt.local?.stop();
|
|
3513
|
+
rt.attached?.stop();
|
|
3287
3514
|
const idle = await rt.tracker.waitIdle(timeoutMs);
|
|
3288
3515
|
if (!idle) {
|
|
3289
3516
|
rt.logger.warn(`${rt.tracker.activeCount} run(s) ainda em execu\xE7\xE3o ap\xF3s ${timeoutMs}ms \u2014 abandonando a espera`);
|
|
@@ -3401,6 +3628,7 @@ var image2 = hobots.image;
|
|
|
3401
3628
|
var isFileRef2 = hobots.isFileRef;
|
|
3402
3629
|
|
|
3403
3630
|
// src/index.ts
|
|
3631
|
+
loadDotenv();
|
|
3404
3632
|
var src_default = hobots;
|
|
3405
3633
|
export {
|
|
3406
3634
|
captureException2 as captureException,
|