hobots 4.1.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 +49 -5
- package/dist/index.cjs +685 -110
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +101 -23
- package/dist/index.d.ts +101 -23
- package/dist/index.js +685 -110
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +50 -1
- package/dist/protocol.d.ts +50 -1
- package/docs/api-reference.md +9 -5
- package/docs/concepts.md +3 -3
- package/docs/getting-started.md +4 -2
- package/docs/protocol.md +21 -3
- package/docs/runs.md +41 -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
|
}
|
|
@@ -139,13 +86,13 @@ async function fetchWithRetry(send, options) {
|
|
|
139
86
|
} catch (error) {
|
|
140
87
|
retriable = error;
|
|
141
88
|
}
|
|
89
|
+
const failure = retriable instanceof Error ? retriable : new Error(String(retriable));
|
|
142
90
|
const base = delays[attempt];
|
|
143
|
-
if (base === void 0)
|
|
144
|
-
throw retriable instanceof Error ? retriable : new Error(String(retriable));
|
|
145
|
-
}
|
|
91
|
+
if (base === void 0 || options.signal?.aborted) throw failure;
|
|
146
92
|
const delay = serverDelay ?? jitter(base);
|
|
147
93
|
options.logger.warn(`${options.label}: falha (${String(retriable)}), retry em ${delay}ms`);
|
|
148
|
-
await sleep(delay);
|
|
94
|
+
await (options.signal ? abortableSleep(delay, options.signal) : sleep(delay));
|
|
95
|
+
if (options.signal?.aborted) throw failure;
|
|
149
96
|
}
|
|
150
97
|
}
|
|
151
98
|
var MAX_RETRY_AFTER_MS = 6e4;
|
|
@@ -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;
|
|
@@ -831,6 +856,7 @@ var ATTACHMENT_TERMINAL_STATUSES = [429, 501, 503];
|
|
|
831
856
|
var ITEMS_TERMINAL_STATUSES = [429];
|
|
832
857
|
var UPLOAD_TIMEOUT_MS = 3e4;
|
|
833
858
|
var UPLOAD_RETRY_DELAYS_MS = [1e3, 4e3];
|
|
859
|
+
var FILE_RETRY_DELAYS_MS = [1e3, 4e3];
|
|
834
860
|
var TasksApiError = class extends Error {
|
|
835
861
|
constructor(message, status) {
|
|
836
862
|
super(message);
|
|
@@ -916,6 +942,42 @@ var TasksApi = class {
|
|
|
916
942
|
created_at: typeof body.created_at === "string" ? body.created_at : (/* @__PURE__ */ new Date()).toISOString()
|
|
917
943
|
};
|
|
918
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
|
+
}
|
|
919
981
|
/** Nunca confia no shape da resposta: runs malformadas são descartadas e o `max` é respeitado. */
|
|
920
982
|
parsePollResponse(body, max) {
|
|
921
983
|
const runs = body?.runs;
|
|
@@ -1018,7 +1080,13 @@ var TasksApi = class {
|
|
|
1018
1080
|
signal: timeoutSignal
|
|
1019
1081
|
})
|
|
1020
1082
|
),
|
|
1021
|
-
{
|
|
1083
|
+
{
|
|
1084
|
+
logger: this.logger,
|
|
1085
|
+
label: `anexo da run ${runId}`,
|
|
1086
|
+
noRetryStatuses: ATTACHMENT_TERMINAL_STATUSES,
|
|
1087
|
+
// run abortada (timeout, 409, kill): o create em voo rejeita por abort e não entra na ladder
|
|
1088
|
+
signal
|
|
1089
|
+
}
|
|
1022
1090
|
);
|
|
1023
1091
|
if (!response.ok) {
|
|
1024
1092
|
throw new TasksApiError(`cria\xE7\xE3o de anexo rejeitada (HTTP ${response.status})`, response.status);
|
|
@@ -1052,7 +1120,7 @@ var TasksApi = class {
|
|
|
1052
1120
|
signal: timeoutSignal
|
|
1053
1121
|
})
|
|
1054
1122
|
),
|
|
1055
|
-
{ delays: UPLOAD_RETRY_DELAYS_MS, logger: this.logger, label: "upload de anexo" }
|
|
1123
|
+
{ delays: UPLOAD_RETRY_DELAYS_MS, logger: this.logger, label: "upload de anexo", signal }
|
|
1056
1124
|
);
|
|
1057
1125
|
if (!response.ok) {
|
|
1058
1126
|
throw new TasksApiError(`upload de anexo rejeitado (HTTP ${response.status})`, response.status);
|
|
@@ -1074,6 +1142,74 @@ var TasksApi = class {
|
|
|
1074
1142
|
throw new TasksApiError(`confirma\xE7\xE3o de anexo rejeitada (HTTP ${response.status})`, response.status);
|
|
1075
1143
|
}
|
|
1076
1144
|
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Pede a URL pré-assinada de GET de um arquivo do formulário. 404 (não existe nesta run)
|
|
1147
|
+
* e 410 (já baixado/expirado) viram `TasksApiError` com o status — quem chama traduz.
|
|
1148
|
+
*/
|
|
1149
|
+
async requestFileDownload(runId, fileId, signal) {
|
|
1150
|
+
const response = await fetchWithRetry(
|
|
1151
|
+
() => withRequestTimeout(
|
|
1152
|
+
UPDATE_TIMEOUT_MS,
|
|
1153
|
+
signal,
|
|
1154
|
+
(timeoutSignal) => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/files/${encodeURIComponent(fileId)}/download`, {
|
|
1155
|
+
redirect: "error",
|
|
1156
|
+
method: "POST",
|
|
1157
|
+
headers: this.headers(),
|
|
1158
|
+
signal: timeoutSignal
|
|
1159
|
+
})
|
|
1160
|
+
),
|
|
1161
|
+
{ delays: FILE_RETRY_DELAYS_MS, logger: this.logger, label: `download do arquivo ${fileId}`, signal }
|
|
1162
|
+
);
|
|
1163
|
+
if (!response.ok) {
|
|
1164
|
+
throw new TasksApiError(`download de arquivo rejeitado (HTTP ${response.status})`, response.status);
|
|
1165
|
+
}
|
|
1166
|
+
const body = await response.json().catch(() => null);
|
|
1167
|
+
if (typeof body?.url !== "string") {
|
|
1168
|
+
throw new TasksApiError("resposta inv\xE1lida do download de arquivo (url ausente)", 0);
|
|
1169
|
+
}
|
|
1170
|
+
return {
|
|
1171
|
+
url: body.url,
|
|
1172
|
+
expires_in: typeof body.expires_in === "number" ? body.expires_in : 0,
|
|
1173
|
+
name: typeof body.name === "string" ? body.name : "",
|
|
1174
|
+
size: typeof body.size === "number" ? body.size : -1,
|
|
1175
|
+
content_type: typeof body.content_type === "string" ? body.content_type : ""
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* GET do binário direto no S3 — deliberadamente sem `authorization` (a autorização
|
|
1180
|
+
* está na assinatura da URL; mandar o client secret para fora da API seria vazamento).
|
|
1181
|
+
* Devolve a Response com o corpo ainda não lido: quem chama faz o streaming para o disco.
|
|
1182
|
+
*/
|
|
1183
|
+
async fetchFile(url, signal) {
|
|
1184
|
+
const response = await fetchWithRetry(() => fetch(url, { redirect: "error", method: "GET", signal }), {
|
|
1185
|
+
delays: FILE_RETRY_DELAYS_MS,
|
|
1186
|
+
logger: this.logger,
|
|
1187
|
+
label: "download de arquivo",
|
|
1188
|
+
signal
|
|
1189
|
+
});
|
|
1190
|
+
if (!response.ok) {
|
|
1191
|
+
throw new TasksApiError(`download de arquivo falhou (HTTP ${response.status})`, response.status);
|
|
1192
|
+
}
|
|
1193
|
+
return response;
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Confirma o download: o servidor apaga o objeto do S3 na hora. Idempotente (204);
|
|
1197
|
+
* 404/410 também significam "não há mais nada a apagar" e contam como confirmado.
|
|
1198
|
+
*/
|
|
1199
|
+
async confirmFileDownload(runId, fileId) {
|
|
1200
|
+
const response = await fetchWithRetry(
|
|
1201
|
+
() => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/files/${encodeURIComponent(fileId)}`, {
|
|
1202
|
+
redirect: "error",
|
|
1203
|
+
method: "DELETE",
|
|
1204
|
+
headers: this.headers(),
|
|
1205
|
+
signal: AbortSignal.timeout(UPDATE_TIMEOUT_MS)
|
|
1206
|
+
}),
|
|
1207
|
+
{ delays: FILE_RETRY_DELAYS_MS, logger: this.logger, label: `confirma\xE7\xE3o do arquivo ${fileId}` }
|
|
1208
|
+
);
|
|
1209
|
+
if (!response.ok && response.status !== 404 && response.status !== 410) {
|
|
1210
|
+
throw new TasksApiError(`confirma\xE7\xE3o de download rejeitada (HTTP ${response.status})`, response.status);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1077
1213
|
};
|
|
1078
1214
|
|
|
1079
1215
|
// src/tasks/backoff.ts
|
|
@@ -1099,7 +1235,7 @@ var Backoff = class {
|
|
|
1099
1235
|
this.authFailures++;
|
|
1100
1236
|
if (this.authFailures === 1 || this.authFailures % 10 === 0) {
|
|
1101
1237
|
this.logger.error(
|
|
1102
|
-
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"
|
|
1103
1239
|
);
|
|
1104
1240
|
}
|
|
1105
1241
|
return Math.max(this.floorMs, AUTH_FAILURE_INTERVAL_MS);
|
|
@@ -1125,6 +1261,9 @@ var TERMINAL_ERROR_NAMES = /* @__PURE__ */ new Set(["RunCanceled", "AbortError",
|
|
|
1125
1261
|
function isTerminalError(error) {
|
|
1126
1262
|
return error instanceof Error && TERMINAL_ERROR_NAMES.has(error.name);
|
|
1127
1263
|
}
|
|
1264
|
+
function isCancellation(error) {
|
|
1265
|
+
return error instanceof Error && (error.name === "RunCanceled" || error.name === "AbortError");
|
|
1266
|
+
}
|
|
1128
1267
|
|
|
1129
1268
|
// src/tasks/each.ts
|
|
1130
1269
|
function thrownMessage(error) {
|
|
@@ -1190,16 +1329,15 @@ async function eachItem(list, handler, options, sink) {
|
|
|
1190
1329
|
if (!handle.reported) handle.succeeded();
|
|
1191
1330
|
} catch (error) {
|
|
1192
1331
|
if (isTerminalError(error) || sink.isAborted()) {
|
|
1193
|
-
step?.setStatus("cancelled");
|
|
1194
|
-
step?.finish();
|
|
1332
|
+
step?.setStatus("cancelled").succeeded();
|
|
1195
1333
|
throw error;
|
|
1196
1334
|
}
|
|
1197
1335
|
const message = thrownMessage(error);
|
|
1198
1336
|
if (!handle.reported) handle.failed(message);
|
|
1199
|
-
step?.
|
|
1337
|
+
step?.failed(error);
|
|
1200
1338
|
sink.logError(`item ${description} falhou: ${message}`);
|
|
1201
1339
|
}
|
|
1202
|
-
step?.
|
|
1340
|
+
step?.succeeded();
|
|
1203
1341
|
if (options.progress !== false) sink.advance();
|
|
1204
1342
|
result.total += 1;
|
|
1205
1343
|
result[handle.status ?? "succeeded"] += 1;
|
|
@@ -1208,6 +1346,255 @@ async function eachItem(list, handler, options, sink) {
|
|
|
1208
1346
|
return result;
|
|
1209
1347
|
}
|
|
1210
1348
|
|
|
1349
|
+
// src/tasks/files.ts
|
|
1350
|
+
var LOCAL_FILES_DIR_ENV = "HOBOTS_LOCAL_FILES_DIR";
|
|
1351
|
+
var FILE_DOWNLOAD_IDLE_TIMEOUT_MS = 6e4;
|
|
1352
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1353
|
+
function isFileRef(value) {
|
|
1354
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1355
|
+
const ref = value;
|
|
1356
|
+
return typeof ref.id === "string" && typeof ref.name === "string" && typeof ref.size === "number" && typeof ref.contentType === "string" && (ref.storage === "s3" || ref.storage === "local");
|
|
1357
|
+
}
|
|
1358
|
+
function fileField(params, field) {
|
|
1359
|
+
if (!Object.prototype.hasOwnProperty.call(params, field)) return void 0;
|
|
1360
|
+
const value = params[field];
|
|
1361
|
+
if (isFileRef(value)) return value;
|
|
1362
|
+
if (Array.isArray(value) && value.every(isFileRef)) return value;
|
|
1363
|
+
return void 0;
|
|
1364
|
+
}
|
|
1365
|
+
function collectFileRefs(params) {
|
|
1366
|
+
const refs = [];
|
|
1367
|
+
for (const field of Object.keys(params)) {
|
|
1368
|
+
const value = fileField(params, field);
|
|
1369
|
+
if (value !== void 0) refs.push(...Array.isArray(value) ? value : [value]);
|
|
1370
|
+
}
|
|
1371
|
+
return refs;
|
|
1372
|
+
}
|
|
1373
|
+
function safeFileName(name, fallback) {
|
|
1374
|
+
const cleaned = name.replace(/^.*[\\/]/, "").replace(/[\u0000-\u001f<>:"|?*]/g, "_").trim();
|
|
1375
|
+
return cleaned === "" || cleaned === "." || cleaned === ".." ? fallback : cleaned;
|
|
1376
|
+
}
|
|
1377
|
+
function isInsideDir(root, candidate, sep) {
|
|
1378
|
+
const base = root.endsWith(sep) ? root : root + sep;
|
|
1379
|
+
return candidate.startsWith(base) && candidate.length > base.length;
|
|
1380
|
+
}
|
|
1381
|
+
function unavailableLocal(ref) {
|
|
1382
|
+
return new Error(`arquivo "${ref.name}" enviado pelo Studio de outro computador ou j\xE1 consumido`);
|
|
1383
|
+
}
|
|
1384
|
+
function errorCode(error) {
|
|
1385
|
+
return error?.code;
|
|
1386
|
+
}
|
|
1387
|
+
var RunFileStore = class {
|
|
1388
|
+
constructor(deps) {
|
|
1389
|
+
this.deps = deps;
|
|
1390
|
+
this.files = {
|
|
1391
|
+
list: () => collectFileRefs(deps.params),
|
|
1392
|
+
get: (field) => fileField(deps.params, field),
|
|
1393
|
+
// async: erro de uso (campo sem arquivo, valor inválido) chega como Promise rejeitada, nunca síncrono
|
|
1394
|
+
download: async (refOrField, options) => this.download(this.single(refOrField), options?.dest),
|
|
1395
|
+
downloadAll: async (field, options) => this.downloadAll(field, options?.dest),
|
|
1396
|
+
read: async (refOrField) => this.read(this.single(refOrField))
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
deps;
|
|
1400
|
+
files;
|
|
1401
|
+
tempDir;
|
|
1402
|
+
/** Apaga a pasta temporária da execução (cópias que o bot não levou para `dest`). Nunca lança. */
|
|
1403
|
+
async cleanup() {
|
|
1404
|
+
const pending = this.tempDir;
|
|
1405
|
+
this.tempDir = void 0;
|
|
1406
|
+
if (!pending) return;
|
|
1407
|
+
try {
|
|
1408
|
+
const { rm } = await import("fs/promises");
|
|
1409
|
+
await rm(await pending, { recursive: true, force: true });
|
|
1410
|
+
} catch {
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
field(field) {
|
|
1414
|
+
const value = fileField(this.deps.params, field);
|
|
1415
|
+
if (value === void 0) throw new Error(`campo "${field}" n\xE3o tem arquivo nos params`);
|
|
1416
|
+
return value;
|
|
1417
|
+
}
|
|
1418
|
+
single(refOrField) {
|
|
1419
|
+
if (typeof refOrField === "string") {
|
|
1420
|
+
const value = this.field(refOrField);
|
|
1421
|
+
if (Array.isArray(value)) {
|
|
1422
|
+
throw new Error(`campo "${refOrField}" tem v\xE1rios arquivos \u2014 use downloadAll("${refOrField}") ou passe um item da lista`);
|
|
1423
|
+
}
|
|
1424
|
+
return value;
|
|
1425
|
+
}
|
|
1426
|
+
if (!isFileRef(refOrField)) throw new TypeError("esperado um FileRef ou o nome de um campo de arquivo");
|
|
1427
|
+
return refOrField;
|
|
1428
|
+
}
|
|
1429
|
+
async downloadAll(field, dest) {
|
|
1430
|
+
const value = this.field(field);
|
|
1431
|
+
const refs = Array.isArray(value) ? value : [value];
|
|
1432
|
+
const paths = [];
|
|
1433
|
+
for (const ref of refs) paths.push(await this.download(ref, dest, true));
|
|
1434
|
+
return paths;
|
|
1435
|
+
}
|
|
1436
|
+
async read(ref) {
|
|
1437
|
+
const { readFile, rm } = await import("fs/promises");
|
|
1438
|
+
const { dirname } = await import("path");
|
|
1439
|
+
const path = await this.download(ref, void 0);
|
|
1440
|
+
try {
|
|
1441
|
+
return await readFile(path);
|
|
1442
|
+
} finally {
|
|
1443
|
+
await rm(dirname(path), { recursive: true, force: true });
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
async download(ref, dest, destIsDir = false) {
|
|
1447
|
+
this.checkCanceled();
|
|
1448
|
+
const target = await this.resolveTarget(ref, dest, destIsDir);
|
|
1449
|
+
if (ref.storage === "local") await this.copyLocal(ref, target);
|
|
1450
|
+
else await this.downloadS3(ref, target);
|
|
1451
|
+
return target;
|
|
1452
|
+
}
|
|
1453
|
+
checkCanceled() {
|
|
1454
|
+
if (this.deps.signal.aborted) throw runCanceledError();
|
|
1455
|
+
}
|
|
1456
|
+
runTempDir() {
|
|
1457
|
+
this.tempDir ??= (async () => {
|
|
1458
|
+
const { mkdtemp } = await import("fs/promises");
|
|
1459
|
+
const { tmpdir } = await import("os");
|
|
1460
|
+
const { join } = await import("path");
|
|
1461
|
+
return mkdtemp(join(tmpdir(), "hobots-files-"));
|
|
1462
|
+
})();
|
|
1463
|
+
return this.tempDir;
|
|
1464
|
+
}
|
|
1465
|
+
async resolveTarget(ref, dest, destIsDir) {
|
|
1466
|
+
const { mkdir, mkdtemp, stat } = await import("fs/promises");
|
|
1467
|
+
const { dirname, join, resolve: resolve2 } = await import("path");
|
|
1468
|
+
const name = safeFileName(ref.name, "arquivo");
|
|
1469
|
+
if (dest === void 0) {
|
|
1470
|
+
return join(await mkdtemp(join(await this.runTempDir(), "f-")), name);
|
|
1471
|
+
}
|
|
1472
|
+
const isDir = destIsDir || dest.endsWith("/") || dest.endsWith("\\") || (await stat(dest).catch(() => void 0))?.isDirectory() === true;
|
|
1473
|
+
if (isDir) {
|
|
1474
|
+
await mkdir(dest, { recursive: true });
|
|
1475
|
+
return resolve2(dest, name);
|
|
1476
|
+
}
|
|
1477
|
+
await mkdir(dirname(resolve2(dest)), { recursive: true });
|
|
1478
|
+
return resolve2(dest);
|
|
1479
|
+
}
|
|
1480
|
+
/** storage 's3': URL pré-assinada → GET em streaming → confere o tamanho → só então DELETE. */
|
|
1481
|
+
async downloadS3(ref, target) {
|
|
1482
|
+
const { api, runId, signal } = this.deps;
|
|
1483
|
+
let expected;
|
|
1484
|
+
let url;
|
|
1485
|
+
try {
|
|
1486
|
+
const info = await api.requestFileDownload(runId, ref.id, signal);
|
|
1487
|
+
expected = info.size >= 0 ? info.size : ref.size;
|
|
1488
|
+
url = info.url;
|
|
1489
|
+
} catch (error) {
|
|
1490
|
+
throw this.translate(error, ref);
|
|
1491
|
+
}
|
|
1492
|
+
const { open, rename, rm } = await import("fs/promises");
|
|
1493
|
+
const part = `${target}.part`;
|
|
1494
|
+
const idleTimeoutMs = this.deps.idleTimeoutMs ?? FILE_DOWNLOAD_IDLE_TIMEOUT_MS;
|
|
1495
|
+
const controller = new AbortController();
|
|
1496
|
+
let timedOut = false;
|
|
1497
|
+
let timer;
|
|
1498
|
+
const arm = () => {
|
|
1499
|
+
clearTimeout(timer);
|
|
1500
|
+
timer = setTimeout(() => {
|
|
1501
|
+
timedOut = true;
|
|
1502
|
+
controller.abort();
|
|
1503
|
+
}, idleTimeoutMs);
|
|
1504
|
+
};
|
|
1505
|
+
const onCancel = () => controller.abort();
|
|
1506
|
+
signal.addEventListener("abort", onCancel, { once: true });
|
|
1507
|
+
try {
|
|
1508
|
+
arm();
|
|
1509
|
+
const response = await api.fetchFile(url, controller.signal);
|
|
1510
|
+
const handle = await open(part, "w");
|
|
1511
|
+
let written = 0;
|
|
1512
|
+
try {
|
|
1513
|
+
const reader = response.body?.getReader();
|
|
1514
|
+
const stopReading = () => void reader?.cancel().catch(() => void 0);
|
|
1515
|
+
controller.signal.addEventListener("abort", stopReading, { once: true });
|
|
1516
|
+
for (; ; ) {
|
|
1517
|
+
const chunk = reader ? await reader.read() : { done: true, value: void 0 };
|
|
1518
|
+
if (chunk.done) break;
|
|
1519
|
+
arm();
|
|
1520
|
+
written += chunk.value.byteLength;
|
|
1521
|
+
if (written > expected) {
|
|
1522
|
+
stopReading();
|
|
1523
|
+
break;
|
|
1524
|
+
}
|
|
1525
|
+
await handle.write(chunk.value);
|
|
1526
|
+
}
|
|
1527
|
+
} finally {
|
|
1528
|
+
await handle.close();
|
|
1529
|
+
}
|
|
1530
|
+
if (written !== expected) {
|
|
1531
|
+
throw new Error(`tamanho divergente no download de "${ref.name}": recebidos ${written} bytes, esperados ${expected}`);
|
|
1532
|
+
}
|
|
1533
|
+
await rename(part, target);
|
|
1534
|
+
} catch (error) {
|
|
1535
|
+
await rm(part, { force: true });
|
|
1536
|
+
if (signal.aborted) throw runCanceledError();
|
|
1537
|
+
if (timedOut) {
|
|
1538
|
+
throw new Error(`download de "${ref.name}" parado h\xE1 ${idleTimeoutMs / 1e3}s \u2014 tente de novo`);
|
|
1539
|
+
}
|
|
1540
|
+
throw this.translate(error, ref);
|
|
1541
|
+
} finally {
|
|
1542
|
+
clearTimeout(timer);
|
|
1543
|
+
signal.removeEventListener("abort", onCancel);
|
|
1544
|
+
}
|
|
1545
|
+
try {
|
|
1546
|
+
await api.confirmFileDownload(runId, ref.id);
|
|
1547
|
+
} catch (error) {
|
|
1548
|
+
this.deps.warn(`arquivo "${ref.name}" baixado, mas a confirma\xE7\xE3o ao servidor falhou: ${String(error)}`);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
/** storage 'local': copia o único arquivo de `$HOBOTS_LOCAL_FILES_DIR/<id>/` e apaga a pasta de origem. */
|
|
1552
|
+
async copyLocal(ref, target) {
|
|
1553
|
+
const base = process.env[LOCAL_FILES_DIR_ENV];
|
|
1554
|
+
if (!base) throw unavailableLocal(ref);
|
|
1555
|
+
const { copyFile, readdir, rename, rm, stat } = await import("fs/promises");
|
|
1556
|
+
const { join, resolve: resolve2, sep } = await import("path");
|
|
1557
|
+
const root = resolve2(base);
|
|
1558
|
+
const folder = resolve2(root, ref.id);
|
|
1559
|
+
if (!isInsideDir(root, folder, sep) || !UUID_RE.test(ref.id)) {
|
|
1560
|
+
throw new Error(`id de arquivo local inv\xE1lido: ${JSON.stringify(ref.id)}`);
|
|
1561
|
+
}
|
|
1562
|
+
let names;
|
|
1563
|
+
try {
|
|
1564
|
+
names = (await readdir(folder, { withFileTypes: true })).filter((entry) => entry.isFile()).map((entry) => entry.name);
|
|
1565
|
+
} catch (error) {
|
|
1566
|
+
if (errorCode(error) === "ENOENT" || errorCode(error) === "ENOTDIR") throw unavailableLocal(ref);
|
|
1567
|
+
throw error;
|
|
1568
|
+
}
|
|
1569
|
+
if (names.length === 0) throw unavailableLocal(ref);
|
|
1570
|
+
if (names.length > 1) throw new Error(`pasta do arquivo "${ref.name}" tem ${names.length} arquivos \u2014 esperado 1`);
|
|
1571
|
+
this.checkCanceled();
|
|
1572
|
+
const part = `${target}.part`;
|
|
1573
|
+
try {
|
|
1574
|
+
await copyFile(join(folder, names[0]), part);
|
|
1575
|
+
const { size } = await stat(part);
|
|
1576
|
+
if (size !== ref.size) {
|
|
1577
|
+
throw new Error(`tamanho divergente na c\xF3pia de "${ref.name}": copiados ${size} bytes, esperados ${ref.size}`);
|
|
1578
|
+
}
|
|
1579
|
+
await rename(part, target);
|
|
1580
|
+
} catch (error) {
|
|
1581
|
+
await rm(part, { force: true });
|
|
1582
|
+
throw error;
|
|
1583
|
+
}
|
|
1584
|
+
await rm(folder, { recursive: true, force: true });
|
|
1585
|
+
}
|
|
1586
|
+
translate(error, ref) {
|
|
1587
|
+
if (this.deps.signal.aborted) return runCanceledError();
|
|
1588
|
+
if (error instanceof TasksApiError && error.status === 410) {
|
|
1589
|
+
return new TasksApiError(`arquivo "${ref.name}" j\xE1 baixado ou expirado`, 410);
|
|
1590
|
+
}
|
|
1591
|
+
if (error instanceof TasksApiError && error.status === 404) {
|
|
1592
|
+
return new TasksApiError(`arquivo "${ref.name}" n\xE3o encontrado nesta execu\xE7\xE3o`, 404);
|
|
1593
|
+
}
|
|
1594
|
+
return error;
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
1597
|
+
|
|
1211
1598
|
// src/retry.ts
|
|
1212
1599
|
var DEFAULT_RETRIES = 3;
|
|
1213
1600
|
var DEFAULT_DELAY_MS = 1e3;
|
|
@@ -1392,7 +1779,7 @@ var RunReporter = class {
|
|
|
1392
1779
|
tx.setStepGuard(checkCanceled);
|
|
1393
1780
|
const eachSink = {
|
|
1394
1781
|
report: (status, message, payload, id, image3) => this.addItem(status, message, payload, id, image3),
|
|
1395
|
-
startStep: (op, description) => tx.
|
|
1782
|
+
startStep: (op, description) => tx.start(op, description),
|
|
1396
1783
|
declareTotal: (total) => {
|
|
1397
1784
|
if (this.progressTotal == null) this.setProgress(this.progressCurrent, total);
|
|
1398
1785
|
},
|
|
@@ -1446,12 +1833,14 @@ var RunReporter = class {
|
|
|
1446
1833
|
);
|
|
1447
1834
|
const signal = this.abortController.signal;
|
|
1448
1835
|
const sleep2 = (ms) => cancelableSleep(ms, signal);
|
|
1836
|
+
this.fileStore = new RunFileStore({ api, runId, params, signal, warn: (message) => this.log("warning", message) });
|
|
1449
1837
|
this.context = {
|
|
1450
1838
|
runId,
|
|
1451
1839
|
params,
|
|
1452
1840
|
log,
|
|
1453
1841
|
items,
|
|
1454
1842
|
progress,
|
|
1843
|
+
files: this.fileStore.files,
|
|
1455
1844
|
transaction: tx,
|
|
1456
1845
|
flush: (timeoutMs) => this.flush(timeoutMs),
|
|
1457
1846
|
signal,
|
|
@@ -1483,8 +1872,17 @@ var RunReporter = class {
|
|
|
1483
1872
|
itemSeq = 0;
|
|
1484
1873
|
/** Servidor sem o recurso (404) — para de tentar (e de avisar) nesta run. */
|
|
1485
1874
|
itemsUnsupported = false;
|
|
1486
|
-
/**
|
|
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
|
+
*/
|
|
1487
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;
|
|
1488
1886
|
/**
|
|
1489
1887
|
* Só o kill() do exit path fecha os itens — `dead` (run cancelada/finalizada no
|
|
1490
1888
|
* servidor) NÃO fecha: o servidor aceita itens de run terminal (trabalho já feito).
|
|
@@ -1506,6 +1904,12 @@ var RunReporter = class {
|
|
|
1506
1904
|
progressDirty = false;
|
|
1507
1905
|
progressTimer;
|
|
1508
1906
|
progressWarned = false;
|
|
1907
|
+
/** Arquivos do formulário desta run (`ctx.files`) — a pasta temporária é apagada no fim. */
|
|
1908
|
+
fileStore;
|
|
1909
|
+
/** Apaga as cópias de arquivo que ficaram na pasta temporária da run. Nunca lança. */
|
|
1910
|
+
cleanupFiles() {
|
|
1911
|
+
return this.fileStore.cleanup();
|
|
1912
|
+
}
|
|
1509
1913
|
get isDead() {
|
|
1510
1914
|
return this.dead;
|
|
1511
1915
|
}
|
|
@@ -1788,6 +2192,7 @@ var RunReporter = class {
|
|
|
1788
2192
|
if (this.itemsUnsupported) return;
|
|
1789
2193
|
try {
|
|
1790
2194
|
await this.api.createItems(this.runId, { items: batch });
|
|
2195
|
+
this.itemsStorageFull = false;
|
|
1791
2196
|
} catch (error) {
|
|
1792
2197
|
if (error instanceof TasksApiError && error.status === 404) {
|
|
1793
2198
|
this.itemsUnsupported = true;
|
|
@@ -1801,6 +2206,13 @@ var RunReporter = class {
|
|
|
1801
2206
|
}
|
|
1802
2207
|
return;
|
|
1803
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
|
+
}
|
|
1804
2216
|
if (error instanceof TasksApiError) {
|
|
1805
2217
|
this.logger.error(`lote de ${batch.length} item(ns) rejeitado pelo servidor:`, error);
|
|
1806
2218
|
this.log("warning", `${batch.length} item(ns) descartado(s): envio rejeitado pelo servidor (HTTP ${error.status})`);
|
|
@@ -2085,16 +2497,19 @@ var Step = class {
|
|
|
2085
2497
|
return this;
|
|
2086
2498
|
}
|
|
2087
2499
|
/**
|
|
2088
|
-
* Marca a step como `error` guardando o motivo em `data.error` ({ type, message })
|
|
2089
|
-
* 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.
|
|
2090
2502
|
* `options.image` pendura um print do estado da tela em `data.attachments`.
|
|
2503
|
+
* Numa step já fechada não faz nada.
|
|
2091
2504
|
*/
|
|
2092
|
-
|
|
2505
|
+
failed(error, message, options) {
|
|
2506
|
+
if (this.endTimestamp) return this;
|
|
2093
2507
|
this.status = "error";
|
|
2094
2508
|
const type = error instanceof Error ? error.name : typeof error === "object" && error !== null ? "Error" : void 0;
|
|
2095
2509
|
const raw = message ?? (error instanceof Error ? error.message || error.name : String(error));
|
|
2096
2510
|
this.setData("error", { ...type ? { type } : {}, message: stepMessage(raw) });
|
|
2097
2511
|
if (options?.image !== void 0) this.attachImage(options.image);
|
|
2512
|
+
this.close();
|
|
2098
2513
|
return this;
|
|
2099
2514
|
}
|
|
2100
2515
|
/**
|
|
@@ -2109,7 +2524,7 @@ var Step = class {
|
|
|
2109
2524
|
if (!id) return;
|
|
2110
2525
|
this.setData("attachments", [...current, id]);
|
|
2111
2526
|
}
|
|
2112
|
-
/** Motivo registrado via setStatus(…, message) ou
|
|
2527
|
+
/** Motivo registrado via setStatus(…, message) ou failed — usado no log de FINISH. */
|
|
2113
2528
|
reason() {
|
|
2114
2529
|
const error = this.data?.error;
|
|
2115
2530
|
const text = typeof error?.message === "string" ? error.message : this.data?.message;
|
|
@@ -2120,10 +2535,18 @@ var Step = class {
|
|
|
2120
2535
|
return this;
|
|
2121
2536
|
}
|
|
2122
2537
|
/** Step aninhada (filha desta step). */
|
|
2123
|
-
|
|
2538
|
+
start(op, description) {
|
|
2124
2539
|
return this.transaction.createStep(this.stepId, op, description);
|
|
2125
2540
|
}
|
|
2126
|
-
|
|
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() {
|
|
2127
2550
|
if (this.endTimestamp) return;
|
|
2128
2551
|
this.endTimestamp = nowISO();
|
|
2129
2552
|
this.transaction.emit(
|
|
@@ -2216,7 +2639,7 @@ var Transaction = class {
|
|
|
2216
2639
|
}
|
|
2217
2640
|
/**
|
|
2218
2641
|
* Conecta a checagem de cancelamento chamada antes de cada step nova — é assim que
|
|
2219
|
-
* `hobots.transaction.
|
|
2642
|
+
* `hobots.transaction.start()` (e `step.start()`) param uma run cancelada.
|
|
2220
2643
|
*/
|
|
2221
2644
|
setStepGuard(guard) {
|
|
2222
2645
|
this.stepGuard = guard;
|
|
@@ -2228,10 +2651,32 @@ var Transaction = class {
|
|
|
2228
2651
|
this.emit("step", "start", stepLabel(op, description));
|
|
2229
2652
|
return step;
|
|
2230
2653
|
}
|
|
2231
|
-
/** Step direta da transaction. */
|
|
2232
|
-
|
|
2654
|
+
/** Step direta da transaction. `op` é o nome usado por `succeeded(name)`/`failed(error, name)`. */
|
|
2655
|
+
start(op, description) {
|
|
2233
2656
|
return this.createStep(this.stepId, op, description);
|
|
2234
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
|
+
}
|
|
2235
2680
|
setTag(key, value) {
|
|
2236
2681
|
this.tags[key] = value;
|
|
2237
2682
|
return this;
|
|
@@ -2269,7 +2714,7 @@ var Transaction = class {
|
|
|
2269
2714
|
remaining.push(step);
|
|
2270
2715
|
}
|
|
2271
2716
|
}
|
|
2272
|
-
for (const step of batch) step.
|
|
2717
|
+
for (const step of batch) step.succeeded();
|
|
2273
2718
|
this.steps.splice(0, this.steps.length, ...remaining);
|
|
2274
2719
|
if (this.client) {
|
|
2275
2720
|
for (let i = 0; i < batch.length; i += MAX_STEPS) {
|
|
@@ -2301,7 +2746,7 @@ var Transaction = class {
|
|
|
2301
2746
|
finish() {
|
|
2302
2747
|
if (this.finished) return;
|
|
2303
2748
|
this.finished = true;
|
|
2304
|
-
for (const step of this.steps) step.
|
|
2749
|
+
for (const step of this.steps) step.succeeded();
|
|
2305
2750
|
const endTimestamp = nowISO();
|
|
2306
2751
|
this.emit(
|
|
2307
2752
|
"transaction",
|
|
@@ -2363,14 +2808,21 @@ async function executeRun(input) {
|
|
|
2363
2808
|
await reporter.finalizeItems();
|
|
2364
2809
|
tx.setStatus("error");
|
|
2365
2810
|
tx.finish();
|
|
2811
|
+
captureRunFailure(client, error, reporter);
|
|
2366
2812
|
reporter.reportFailure(toRunError(error));
|
|
2367
2813
|
logger.debug(`run ${run.id} falhou: ${String(error)}`);
|
|
2368
2814
|
}
|
|
2369
2815
|
await reporter.finalize();
|
|
2370
2816
|
} finally {
|
|
2817
|
+
await reporter.cleanupFiles();
|
|
2371
2818
|
tracker.remove(reporter);
|
|
2372
2819
|
}
|
|
2373
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
|
+
}
|
|
2374
2826
|
function warnIncompleteProgress(reporter) {
|
|
2375
2827
|
const snapshot = reporter.context.progress.snapshot;
|
|
2376
2828
|
if (!snapshot || snapshot.current >= snapshot.total) return;
|
|
@@ -2562,6 +3014,86 @@ var TaskAgent = class {
|
|
|
2562
3014
|
}
|
|
2563
3015
|
};
|
|
2564
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
|
+
|
|
2565
3097
|
// src/tasks/item-columns.ts
|
|
2566
3098
|
var KEY_RE = /^[a-zA-Z0-9_.-]{1,100}$/;
|
|
2567
3099
|
var MAX_COLUMNS = WIRE_LIMITS.MAX_ITEM_COLUMNS;
|
|
@@ -2767,11 +3299,18 @@ function hostname() {
|
|
|
2767
3299
|
return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
|
|
2768
3300
|
}
|
|
2769
3301
|
function createRuntime(options, handler) {
|
|
3302
|
+
loadDotenv();
|
|
2770
3303
|
const config = resolveConfig({ clientSecret: options.clientSecret });
|
|
2771
3304
|
const logger = makeLogger(options.debug ?? false);
|
|
2772
3305
|
const environment = resolveEnvironmentOption(options.environment);
|
|
2773
3306
|
const client = new Client(
|
|
2774
|
-
{
|
|
3307
|
+
{
|
|
3308
|
+
environment,
|
|
3309
|
+
release: options.release,
|
|
3310
|
+
tags: options.tags,
|
|
3311
|
+
debug: options.debug,
|
|
3312
|
+
captureRunErrors: options.captureUnhandled !== false
|
|
3313
|
+
},
|
|
2775
3314
|
config
|
|
2776
3315
|
);
|
|
2777
3316
|
const api = new TasksApi({ baseUrl: config.runsBaseUrl, key: config.key, logger });
|
|
@@ -2787,6 +3326,8 @@ function createRuntime(options, handler) {
|
|
|
2787
3326
|
heartbeat: void 0,
|
|
2788
3327
|
agent: void 0,
|
|
2789
3328
|
local: void 0,
|
|
3329
|
+
attached: void 0,
|
|
3330
|
+
runId: void 0,
|
|
2790
3331
|
instanceId: resolveInstanceId(options.instanceId),
|
|
2791
3332
|
environment,
|
|
2792
3333
|
pollIntervalMs: void 0,
|
|
@@ -2823,7 +3364,9 @@ function resolveRunOptions(options, mode, logger) {
|
|
|
2823
3364
|
}
|
|
2824
3365
|
function instanceInfo(rt) {
|
|
2825
3366
|
return {
|
|
3367
|
+
/* v8 ignore start -- defensivo: o boot define o instanceId antes de qualquer poll/criação de run */
|
|
2826
3368
|
instance_id: rt.instanceId ?? "unknown",
|
|
3369
|
+
/* v8 ignore stop */
|
|
2827
3370
|
version: SDK_VERSION,
|
|
2828
3371
|
host: hostname(),
|
|
2829
3372
|
// ambiente da instância: junto do heartbeat, é como o servidor sabe onde o robô roda
|
|
@@ -2862,6 +3405,20 @@ function launchLocal(rt) {
|
|
|
2862
3405
|
rt.local = runner;
|
|
2863
3406
|
void runner.run();
|
|
2864
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
|
+
}
|
|
2865
3422
|
async function boot(rt) {
|
|
2866
3423
|
if (!rt.instanceId) {
|
|
2867
3424
|
const id = await fallbackInstanceId();
|
|
@@ -2881,8 +3438,9 @@ async function boot(rt) {
|
|
|
2881
3438
|
if (rt.options.installExitHandlers !== false) {
|
|
2882
3439
|
rt.uninstallShutdown = installShutdownHandlers(rt.tracker);
|
|
2883
3440
|
}
|
|
2884
|
-
if (rt.handler.mode === "
|
|
2885
|
-
else
|
|
3441
|
+
if (rt.handler.mode === "local") launchLocal(rt);
|
|
3442
|
+
else if (rt.runId) launchAttached(rt, rt.runId);
|
|
3443
|
+
else ensureAgent(rt).start();
|
|
2886
3444
|
}
|
|
2887
3445
|
function scheduleBoot(rt) {
|
|
2888
3446
|
if (rt.bootScheduled) return;
|
|
@@ -2900,6 +3458,7 @@ function startAgent(fn, mode, handler, options) {
|
|
|
2900
3458
|
const agentHandler = { mode, handler, options: resolveRunOptions(options, mode, logger) };
|
|
2901
3459
|
const rt = createRuntime(options, agentHandler);
|
|
2902
3460
|
if (mode === "remote" && options.pollIntervalMs !== void 0) rt.pollIntervalMs = options.pollIntervalMs;
|
|
3461
|
+
if (mode === "remote") rt.runId = resolveRunId(options.runId);
|
|
2903
3462
|
runtime = rt;
|
|
2904
3463
|
scheduleBoot(rt);
|
|
2905
3464
|
}
|
|
@@ -2940,7 +3499,9 @@ function captureException(error, hint) {
|
|
|
2940
3499
|
const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
2941
3500
|
forwardAttachment(rt, hint.attachment, message, hint.type ?? "error");
|
|
2942
3501
|
}
|
|
2943
|
-
|
|
3502
|
+
const runId = currentReporter()?.runId;
|
|
3503
|
+
const withRun = runId ? { ...hint, tags: { run_id: runId, ...hint?.tags } } : hint;
|
|
3504
|
+
return rt.client.captureException(error, withRun);
|
|
2944
3505
|
}
|
|
2945
3506
|
async function close(timeoutMs = 3e4) {
|
|
2946
3507
|
const rt = runtime;
|
|
@@ -2949,6 +3510,7 @@ async function close(timeoutMs = 3e4) {
|
|
|
2949
3510
|
await rt.bootPromise;
|
|
2950
3511
|
if (rt.agent) await rt.agent.stop(0);
|
|
2951
3512
|
rt.local?.stop();
|
|
3513
|
+
rt.attached?.stop();
|
|
2952
3514
|
const idle = await rt.tracker.waitIdle(timeoutMs);
|
|
2953
3515
|
if (!idle) {
|
|
2954
3516
|
rt.logger.warn(`${rt.tracker.activeCount} run(s) ainda em execu\xE7\xE3o ap\xF3s ${timeoutMs}ms \u2014 abandonando a espera`);
|
|
@@ -3000,6 +3562,14 @@ var fallbackItems = {
|
|
|
3000
3562
|
each: (list, handler, options = {}) => eachItem(list, handler, options, fallbackEachSink)
|
|
3001
3563
|
};
|
|
3002
3564
|
var fallbackProgress = { total: noop, advance: noop, set: noop, current: 0, snapshot: null };
|
|
3565
|
+
var outsideRunFiles = () => Promise.reject(new Error("hobots.files s\xF3 baixa arquivos dentro de uma execu\xE7\xE3o (handler do register()/start())"));
|
|
3566
|
+
var fallbackFiles = {
|
|
3567
|
+
list: () => [],
|
|
3568
|
+
get: () => void 0,
|
|
3569
|
+
download: outsideRunFiles,
|
|
3570
|
+
downloadAll: outsideRunFiles,
|
|
3571
|
+
read: outsideRunFiles
|
|
3572
|
+
};
|
|
3003
3573
|
var neverAborts = new AbortController().signal;
|
|
3004
3574
|
var detachedTransaction;
|
|
3005
3575
|
function resolve(member) {
|
|
@@ -3021,6 +3591,7 @@ var hobots = Object.defineProperties({}, {
|
|
|
3021
3591
|
log: getter("log", () => fallbackLog),
|
|
3022
3592
|
items: getter("items", () => fallbackItems),
|
|
3023
3593
|
progress: getter("progress", () => fallbackProgress),
|
|
3594
|
+
files: getter("files", () => fallbackFiles),
|
|
3024
3595
|
transaction: getter("transaction", () => detachedTransaction ??= new Transaction(void 0, "hobots.detached")),
|
|
3025
3596
|
signal: getter("signal", () => neverAborts),
|
|
3026
3597
|
throwIfCanceled: method(() => resolve("throwIfCanceled")?.throwIfCanceled()),
|
|
@@ -3041,7 +3612,8 @@ var hobots = Object.defineProperties({}, {
|
|
|
3041
3612
|
}),
|
|
3042
3613
|
close: method((timeoutMs) => close(timeoutMs)),
|
|
3043
3614
|
captureException: method((error, hint) => captureException(error, hint)),
|
|
3044
|
-
image: method(image)
|
|
3615
|
+
image: method(image),
|
|
3616
|
+
isFileRef: method(isFileRef)
|
|
3045
3617
|
});
|
|
3046
3618
|
function installGlobalHobots() {
|
|
3047
3619
|
if (globalThis.hobots === hobots) return;
|
|
@@ -3053,8 +3625,10 @@ var close2 = hobots.close;
|
|
|
3053
3625
|
var captureException2 = hobots.captureException;
|
|
3054
3626
|
var retry = hobots.retry;
|
|
3055
3627
|
var image2 = hobots.image;
|
|
3628
|
+
var isFileRef2 = hobots.isFileRef;
|
|
3056
3629
|
|
|
3057
3630
|
// src/index.ts
|
|
3631
|
+
loadDotenv();
|
|
3058
3632
|
var src_default = hobots;
|
|
3059
3633
|
export {
|
|
3060
3634
|
captureException2 as captureException,
|
|
@@ -3062,6 +3636,7 @@ export {
|
|
|
3062
3636
|
src_default as default,
|
|
3063
3637
|
hobots,
|
|
3064
3638
|
image2 as image,
|
|
3639
|
+
isFileRef2 as isFileRef,
|
|
3065
3640
|
register2 as register,
|
|
3066
3641
|
retry,
|
|
3067
3642
|
start2 as start
|