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.cjs
CHANGED
|
@@ -35,68 +35,16 @@ __export(src_exports, {
|
|
|
35
35
|
default: () => src_default,
|
|
36
36
|
hobots: () => hobots,
|
|
37
37
|
image: () => image2,
|
|
38
|
+
isFileRef: () => isFileRef2,
|
|
38
39
|
register: () => register2,
|
|
39
40
|
retry: () => retry,
|
|
40
41
|
start: () => start2
|
|
41
42
|
});
|
|
42
43
|
module.exports = __toCommonJS(src_exports);
|
|
43
44
|
|
|
44
|
-
// src/stacktrace.ts
|
|
45
|
-
var MAX_FRAMES = 50;
|
|
46
|
-
var MAX_FILENAME_CHARS = 1024;
|
|
47
|
-
var FRAME_RE = /^\s*at\s+(?:async\s+)?(?:(.+?)\s+\()?(.+?)\)?$/;
|
|
48
|
-
var LOCATION_RE = /^(.*?):(\d+):(\d+)$/;
|
|
49
|
-
function isInApp(filename) {
|
|
50
|
-
if (!filename) return false;
|
|
51
|
-
if (filename.startsWith("node:")) return false;
|
|
52
|
-
if (filename.startsWith("internal/")) return false;
|
|
53
|
-
if (filename.includes("node_modules")) return false;
|
|
54
|
-
if (filename === "<anonymous>") return false;
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
function parseStacktrace(stack) {
|
|
58
|
-
if (!stack) return [];
|
|
59
|
-
const frames = [];
|
|
60
|
-
for (const line of stack.split("\n")) {
|
|
61
|
-
if (frames.length >= MAX_FRAMES) break;
|
|
62
|
-
const match = FRAME_RE.exec(line);
|
|
63
|
-
if (!match || !line.trimStart().startsWith("at ")) continue;
|
|
64
|
-
const [, fn, location] = match;
|
|
65
|
-
const loc = LOCATION_RE.exec(location ?? "");
|
|
66
|
-
const filename = loc ? loc[1] : location;
|
|
67
|
-
frames.push({
|
|
68
|
-
function: fn || "<anonymous>",
|
|
69
|
-
filename: filename ? filename.slice(0, MAX_FILENAME_CHARS) : void 0,
|
|
70
|
-
lineno: loc?.[2] ? Number(loc[2]) : void 0,
|
|
71
|
-
colno: loc?.[3] ? Number(loc[3]) : void 0,
|
|
72
|
-
in_app: isInApp(filename)
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
return frames;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// src/scrub.ts
|
|
79
|
-
var SECRET_PATTERNS = [
|
|
80
|
-
// client secret da Hobots
|
|
81
|
-
[/hb_[a-f0-9]{16,}/gi, "[REDACTED]"],
|
|
82
|
-
// tokens bearer em headers/mensagens
|
|
83
|
-
[/\b(authorization:?\s*)?bearer\s+[a-z0-9._~+/=-]{8,}/gi, "Bearer [REDACTED]"],
|
|
84
|
-
// chaves no formato sk-… (OpenAI e afins)
|
|
85
|
-
[/\bsk-[a-z0-9_-]{16,}\b/gi, "[REDACTED]"],
|
|
86
|
-
// credenciais embutidas em URL (https://user:pass@host)
|
|
87
|
-
[/\/\/[^/\s:@]+:[^/\s@]+@/g, "//[REDACTED]@"]
|
|
88
|
-
];
|
|
89
|
-
function scrubSecrets(text) {
|
|
90
|
-
let out = text;
|
|
91
|
-
for (const [pattern, replacement] of SECRET_PATTERNS) {
|
|
92
|
-
out = out.replace(pattern, replacement);
|
|
93
|
-
}
|
|
94
|
-
return out;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
45
|
// src/utils.ts
|
|
98
46
|
var SDK_NAME = "hobots";
|
|
99
|
-
var SDK_VERSION = true ? "4.
|
|
47
|
+
var SDK_VERSION = true ? "4.5.0" : "0.0.0-dev";
|
|
100
48
|
function isNode() {
|
|
101
49
|
return typeof process !== "undefined" && typeof process.versions?.node === "string";
|
|
102
50
|
}
|
|
@@ -178,13 +126,13 @@ async function fetchWithRetry(send, options) {
|
|
|
178
126
|
} catch (error) {
|
|
179
127
|
retriable = error;
|
|
180
128
|
}
|
|
129
|
+
const failure = retriable instanceof Error ? retriable : new Error(String(retriable));
|
|
181
130
|
const base = delays[attempt];
|
|
182
|
-
if (base === void 0)
|
|
183
|
-
throw retriable instanceof Error ? retriable : new Error(String(retriable));
|
|
184
|
-
}
|
|
131
|
+
if (base === void 0 || options.signal?.aborted) throw failure;
|
|
185
132
|
const delay = serverDelay ?? jitter(base);
|
|
186
133
|
options.logger.warn(`${options.label}: falha (${String(retriable)}), retry em ${delay}ms`);
|
|
187
|
-
await sleep(delay);
|
|
134
|
+
await (options.signal ? abortableSleep(delay, options.signal) : sleep(delay));
|
|
135
|
+
if (options.signal?.aborted) throw failure;
|
|
188
136
|
}
|
|
189
137
|
}
|
|
190
138
|
var MAX_RETRY_AFTER_MS = 6e4;
|
|
@@ -244,6 +192,104 @@ function colorize(text, color) {
|
|
|
244
192
|
return `${ANSI[color]}${text}${ANSI.reset}`;
|
|
245
193
|
}
|
|
246
194
|
|
|
195
|
+
// src/env.ts
|
|
196
|
+
var INSTANCE_ID_ENV = "HOBOTS_INSTANCE_ID";
|
|
197
|
+
var ENVIRONMENT_ENV = "HOBOTS_ENVIRONMENT";
|
|
198
|
+
var RUN_ID_ENV = "HOBOTS_RUN_ID";
|
|
199
|
+
var dotenvLoaded = false;
|
|
200
|
+
function loadDotenv() {
|
|
201
|
+
if (dotenvLoaded) return;
|
|
202
|
+
dotenvLoaded = true;
|
|
203
|
+
if (!isNode() || typeof process.loadEnvFile !== "function") return;
|
|
204
|
+
try {
|
|
205
|
+
process.loadEnvFile(".env");
|
|
206
|
+
} catch (err) {
|
|
207
|
+
const code = err?.code;
|
|
208
|
+
if (code === "ENOENT") return;
|
|
209
|
+
console.warn(`[hobots] n\xE3o foi poss\xEDvel ler o .env da pasta de trabalho (${typeof code === "string" ? code : "erro desconhecido"}) \u2014 seguindo s\xF3 com as vari\xE1veis do ambiente`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function resolveInstanceId(option) {
|
|
213
|
+
const fromOption = option?.trim();
|
|
214
|
+
if (fromOption) return fromOption;
|
|
215
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
216
|
+
return process.env[INSTANCE_ID_ENV]?.trim() || void 0;
|
|
217
|
+
}
|
|
218
|
+
function resolveRunId(option) {
|
|
219
|
+
const fromOption = option?.trim();
|
|
220
|
+
if (fromOption) return fromOption;
|
|
221
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
222
|
+
return process.env[RUN_ID_ENV]?.trim() || void 0;
|
|
223
|
+
}
|
|
224
|
+
function resolveEnvironmentOption(option) {
|
|
225
|
+
const fromOption = option?.trim();
|
|
226
|
+
if (fromOption) return fromOption;
|
|
227
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
228
|
+
return process.env[ENVIRONMENT_ENV]?.trim() || void 0;
|
|
229
|
+
}
|
|
230
|
+
async function fallbackInstanceId() {
|
|
231
|
+
if (!isNode()) return "browser";
|
|
232
|
+
try {
|
|
233
|
+
const os = await import("os");
|
|
234
|
+
return os.hostname() || "unknown-host";
|
|
235
|
+
} catch {
|
|
236
|
+
return "unknown-host";
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/stacktrace.ts
|
|
241
|
+
var MAX_FRAMES = 50;
|
|
242
|
+
var MAX_FILENAME_CHARS = 1024;
|
|
243
|
+
var FRAME_RE = /^\s*at\s+(?:async\s+)?(?:(.+?)\s+\()?(.+?)\)?$/;
|
|
244
|
+
var LOCATION_RE = /^(.*?):(\d+):(\d+)$/;
|
|
245
|
+
function isInApp(filename) {
|
|
246
|
+
if (!filename) return false;
|
|
247
|
+
if (filename.startsWith("node:")) return false;
|
|
248
|
+
if (filename.startsWith("internal/")) return false;
|
|
249
|
+
if (filename.includes("node_modules")) return false;
|
|
250
|
+
if (filename === "<anonymous>") return false;
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
function parseStacktrace(stack) {
|
|
254
|
+
if (!stack) return [];
|
|
255
|
+
const frames = [];
|
|
256
|
+
for (const line of stack.split("\n")) {
|
|
257
|
+
if (frames.length >= MAX_FRAMES) break;
|
|
258
|
+
const match = FRAME_RE.exec(line);
|
|
259
|
+
if (!match || !line.trimStart().startsWith("at ")) continue;
|
|
260
|
+
const [, fn, location] = match;
|
|
261
|
+
const loc = LOCATION_RE.exec(location ?? "");
|
|
262
|
+
const filename = loc ? loc[1] : location;
|
|
263
|
+
frames.push({
|
|
264
|
+
function: fn || "<anonymous>",
|
|
265
|
+
filename: filename ? filename.slice(0, MAX_FILENAME_CHARS) : void 0,
|
|
266
|
+
lineno: loc?.[2] ? Number(loc[2]) : void 0,
|
|
267
|
+
colno: loc?.[3] ? Number(loc[3]) : void 0,
|
|
268
|
+
in_app: isInApp(filename)
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
return frames;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/scrub.ts
|
|
275
|
+
var SECRET_PATTERNS = [
|
|
276
|
+
// client secret da Hobots
|
|
277
|
+
[/hb_[a-f0-9]{16,}/gi, "[REDACTED]"],
|
|
278
|
+
// tokens bearer em headers/mensagens
|
|
279
|
+
[/\b(authorization:?\s*)?bearer\s+[a-z0-9._~+/=-]{8,}/gi, "Bearer [REDACTED]"],
|
|
280
|
+
// chaves no formato sk-… (OpenAI e afins)
|
|
281
|
+
[/\bsk-[a-z0-9_-]{16,}\b/gi, "[REDACTED]"],
|
|
282
|
+
// credenciais embutidas em URL (https://user:pass@host)
|
|
283
|
+
[/\/\/[^/\s:@]+:[^/\s@]+@/g, "//[REDACTED]@"]
|
|
284
|
+
];
|
|
285
|
+
function scrubSecrets(text) {
|
|
286
|
+
let out = text;
|
|
287
|
+
for (const [pattern, replacement] of SECRET_PATTERNS) {
|
|
288
|
+
out = out.replace(pattern, replacement);
|
|
289
|
+
}
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
|
|
247
293
|
// src/eventbuilder.ts
|
|
248
294
|
var MAX_EXCEPTION_VALUE_CHARS = 8192;
|
|
249
295
|
function eventFromException(error, type = "error") {
|
|
@@ -421,6 +467,10 @@ function clipFingerprint(fingerprint) {
|
|
|
421
467
|
if (fingerprint === void 0) return void 0;
|
|
422
468
|
return fingerprint.slice(0, MAX_FINGERPRINT_ITEMS).map((item) => truncate(String(item), MAX_FINGERPRINT_ITEM_CHARS));
|
|
423
469
|
}
|
|
470
|
+
var captured = /* @__PURE__ */ new WeakSet();
|
|
471
|
+
function wasCaptured(error) {
|
|
472
|
+
return typeof error === "object" && error !== null && captured.has(error);
|
|
473
|
+
}
|
|
424
474
|
function runtimeContexts() {
|
|
425
475
|
if (isNode()) {
|
|
426
476
|
return {
|
|
@@ -472,6 +522,7 @@ var Client = class {
|
|
|
472
522
|
return `Client { baseUrl: '${this.#config.baseUrl}' }`;
|
|
473
523
|
}
|
|
474
524
|
captureException(error, hint) {
|
|
525
|
+
if (typeof error === "object" && error !== null) captured.add(error);
|
|
475
526
|
return this.captureEvent(eventFromException(error, hint?.type ?? "error"), hint);
|
|
476
527
|
}
|
|
477
528
|
captureEvent(event, hint) {
|
|
@@ -579,12 +630,12 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
579
630
|
const origin = fromOption ? "clientSecret" : `env ${CLIENT_SECRET_ENV}`;
|
|
580
631
|
if (!key) {
|
|
581
632
|
throw new Error(
|
|
582
|
-
`${prefix} Client Secret ausente \u2014 defina a env ${CLIENT_SECRET_ENV} (ou passe clientSecret nas op\xE7\xF5es do register/start); copie o Client Secret
|
|
633
|
+
`${prefix} Client Secret ausente \u2014 defina a env ${CLIENT_SECRET_ENV} (ou passe clientSecret nas op\xE7\xF5es do register/start); copie o Client Secret na Central: abra o agente \u2192 Configura\xE7\xF5es \u2192 agente \u2192 aba credenciais`
|
|
583
634
|
);
|
|
584
635
|
}
|
|
585
636
|
if (!SECRET_PATTERN.test(key)) {
|
|
586
637
|
throw new Error(
|
|
587
|
-
`${prefix} ${origin} com formato inv\xE1lido (${key.length} caracteres) \u2014 esperado "hb_" + 64 hex; copie o Client Secret
|
|
638
|
+
`${prefix} ${origin} com formato inv\xE1lido (${key.length} caracteres) \u2014 esperado "hb_" + 64 hex; copie o Client Secret na Central: abra o agente \u2192 Configura\xE7\xF5es \u2192 agente \u2192 aba credenciais`
|
|
588
639
|
);
|
|
589
640
|
}
|
|
590
641
|
const baseUrl = validateBaseUrl(envBaseUrl() ?? DEFAULT_BASE_URL, prefix);
|
|
@@ -596,31 +647,6 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
596
647
|
};
|
|
597
648
|
}
|
|
598
649
|
|
|
599
|
-
// src/env.ts
|
|
600
|
-
var INSTANCE_ID_ENV = "HOBOTS_INSTANCE_ID";
|
|
601
|
-
var ENVIRONMENT_ENV = "HOBOTS_ENVIRONMENT";
|
|
602
|
-
function resolveInstanceId(option) {
|
|
603
|
-
const fromOption = option?.trim();
|
|
604
|
-
if (fromOption) return fromOption;
|
|
605
|
-
if (typeof process === "undefined" || !process.env) return void 0;
|
|
606
|
-
return process.env[INSTANCE_ID_ENV]?.trim() || void 0;
|
|
607
|
-
}
|
|
608
|
-
function resolveEnvironmentOption(option) {
|
|
609
|
-
const fromOption = option?.trim();
|
|
610
|
-
if (fromOption) return fromOption;
|
|
611
|
-
if (typeof process === "undefined" || !process.env) return void 0;
|
|
612
|
-
return process.env[ENVIRONMENT_ENV]?.trim() || void 0;
|
|
613
|
-
}
|
|
614
|
-
async function fallbackInstanceId() {
|
|
615
|
-
if (!isNode()) return "browser";
|
|
616
|
-
try {
|
|
617
|
-
const os = await import("os");
|
|
618
|
-
return os.hostname() || "unknown-host";
|
|
619
|
-
} catch {
|
|
620
|
-
return "unknown-host";
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
|
|
624
650
|
// src/heartbeat.ts
|
|
625
651
|
var DEFAULT_INTERVAL_MS = WIRE_LIMITS.HEARTBEAT_DEFAULT_INTERVAL_MS;
|
|
626
652
|
var MIN_REPORTED_INTERVAL_MS = WIRE_LIMITS.HEARTBEAT_MIN_INTERVAL_MS;
|
|
@@ -896,6 +922,7 @@ var ATTACHMENT_TERMINAL_STATUSES = [429, 501, 503];
|
|
|
896
922
|
var ITEMS_TERMINAL_STATUSES = [429];
|
|
897
923
|
var UPLOAD_TIMEOUT_MS = 3e4;
|
|
898
924
|
var UPLOAD_RETRY_DELAYS_MS = [1e3, 4e3];
|
|
925
|
+
var FILE_RETRY_DELAYS_MS = [1e3, 4e3];
|
|
899
926
|
var TasksApiError = class extends Error {
|
|
900
927
|
constructor(message, status) {
|
|
901
928
|
super(message);
|
|
@@ -981,6 +1008,42 @@ var TasksApi = class {
|
|
|
981
1008
|
created_at: typeof body.created_at === "string" ? body.created_at : (/* @__PURE__ */ new Date()).toISOString()
|
|
982
1009
|
};
|
|
983
1010
|
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Anexa o processo a uma run já reivindicada para esta instância (env HOBOTS_RUN_ID):
|
|
1013
|
+
* POST /v1/runs/:id/start. Um request só — quem decide o retry é o AttachedRunner.
|
|
1014
|
+
* 404 (não existe) e 409 (cancelada, já iniciada ou de outra instância) viram
|
|
1015
|
+
* TasksApiError com o status.
|
|
1016
|
+
*/
|
|
1017
|
+
async startRun(runId, request, signal) {
|
|
1018
|
+
const { signal: timeoutSignal, cleanup } = withTimeoutSignal(UPDATE_TIMEOUT_MS, signal);
|
|
1019
|
+
let response;
|
|
1020
|
+
try {
|
|
1021
|
+
response = await fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/start`, {
|
|
1022
|
+
redirect: "error",
|
|
1023
|
+
method: "POST",
|
|
1024
|
+
headers: this.headers(),
|
|
1025
|
+
body: JSON.stringify(request),
|
|
1026
|
+
signal: timeoutSignal
|
|
1027
|
+
});
|
|
1028
|
+
} finally {
|
|
1029
|
+
cleanup();
|
|
1030
|
+
}
|
|
1031
|
+
if (!response.ok) {
|
|
1032
|
+
throw new TasksApiError(`in\xEDcio da run falhou (HTTP ${response.status})`, response.status);
|
|
1033
|
+
}
|
|
1034
|
+
const body = await response.json().catch(() => null);
|
|
1035
|
+
if (typeof body?.id !== "string") {
|
|
1036
|
+
throw new TasksApiError("resposta inv\xE1lida do in\xEDcio da run (id ausente)", 0);
|
|
1037
|
+
}
|
|
1038
|
+
const form = body.form;
|
|
1039
|
+
return {
|
|
1040
|
+
id: body.id,
|
|
1041
|
+
params: body.params && typeof body.params === "object" && !Array.isArray(body.params) ? body.params : {},
|
|
1042
|
+
form: typeof form?.id === "string" && typeof form.name === "string" ? { id: form.id, name: form.name } : null,
|
|
1043
|
+
source: typeof body.source === "string" ? body.source : "hobots",
|
|
1044
|
+
created_at: typeof body.created_at === "string" ? body.created_at : (/* @__PURE__ */ new Date()).toISOString()
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
984
1047
|
/** Nunca confia no shape da resposta: runs malformadas são descartadas e o `max` é respeitado. */
|
|
985
1048
|
parsePollResponse(body, max) {
|
|
986
1049
|
const runs = body?.runs;
|
|
@@ -1083,7 +1146,13 @@ var TasksApi = class {
|
|
|
1083
1146
|
signal: timeoutSignal
|
|
1084
1147
|
})
|
|
1085
1148
|
),
|
|
1086
|
-
{
|
|
1149
|
+
{
|
|
1150
|
+
logger: this.logger,
|
|
1151
|
+
label: `anexo da run ${runId}`,
|
|
1152
|
+
noRetryStatuses: ATTACHMENT_TERMINAL_STATUSES,
|
|
1153
|
+
// run abortada (timeout, 409, kill): o create em voo rejeita por abort e não entra na ladder
|
|
1154
|
+
signal
|
|
1155
|
+
}
|
|
1087
1156
|
);
|
|
1088
1157
|
if (!response.ok) {
|
|
1089
1158
|
throw new TasksApiError(`cria\xE7\xE3o de anexo rejeitada (HTTP ${response.status})`, response.status);
|
|
@@ -1117,7 +1186,7 @@ var TasksApi = class {
|
|
|
1117
1186
|
signal: timeoutSignal
|
|
1118
1187
|
})
|
|
1119
1188
|
),
|
|
1120
|
-
{ delays: UPLOAD_RETRY_DELAYS_MS, logger: this.logger, label: "upload de anexo" }
|
|
1189
|
+
{ delays: UPLOAD_RETRY_DELAYS_MS, logger: this.logger, label: "upload de anexo", signal }
|
|
1121
1190
|
);
|
|
1122
1191
|
if (!response.ok) {
|
|
1123
1192
|
throw new TasksApiError(`upload de anexo rejeitado (HTTP ${response.status})`, response.status);
|
|
@@ -1139,6 +1208,74 @@ var TasksApi = class {
|
|
|
1139
1208
|
throw new TasksApiError(`confirma\xE7\xE3o de anexo rejeitada (HTTP ${response.status})`, response.status);
|
|
1140
1209
|
}
|
|
1141
1210
|
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Pede a URL pré-assinada de GET de um arquivo do formulário. 404 (não existe nesta run)
|
|
1213
|
+
* e 410 (já baixado/expirado) viram `TasksApiError` com o status — quem chama traduz.
|
|
1214
|
+
*/
|
|
1215
|
+
async requestFileDownload(runId, fileId, signal) {
|
|
1216
|
+
const response = await fetchWithRetry(
|
|
1217
|
+
() => withRequestTimeout(
|
|
1218
|
+
UPDATE_TIMEOUT_MS,
|
|
1219
|
+
signal,
|
|
1220
|
+
(timeoutSignal) => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/files/${encodeURIComponent(fileId)}/download`, {
|
|
1221
|
+
redirect: "error",
|
|
1222
|
+
method: "POST",
|
|
1223
|
+
headers: this.headers(),
|
|
1224
|
+
signal: timeoutSignal
|
|
1225
|
+
})
|
|
1226
|
+
),
|
|
1227
|
+
{ delays: FILE_RETRY_DELAYS_MS, logger: this.logger, label: `download do arquivo ${fileId}`, signal }
|
|
1228
|
+
);
|
|
1229
|
+
if (!response.ok) {
|
|
1230
|
+
throw new TasksApiError(`download de arquivo rejeitado (HTTP ${response.status})`, response.status);
|
|
1231
|
+
}
|
|
1232
|
+
const body = await response.json().catch(() => null);
|
|
1233
|
+
if (typeof body?.url !== "string") {
|
|
1234
|
+
throw new TasksApiError("resposta inv\xE1lida do download de arquivo (url ausente)", 0);
|
|
1235
|
+
}
|
|
1236
|
+
return {
|
|
1237
|
+
url: body.url,
|
|
1238
|
+
expires_in: typeof body.expires_in === "number" ? body.expires_in : 0,
|
|
1239
|
+
name: typeof body.name === "string" ? body.name : "",
|
|
1240
|
+
size: typeof body.size === "number" ? body.size : -1,
|
|
1241
|
+
content_type: typeof body.content_type === "string" ? body.content_type : ""
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* GET do binário direto no S3 — deliberadamente sem `authorization` (a autorização
|
|
1246
|
+
* está na assinatura da URL; mandar o client secret para fora da API seria vazamento).
|
|
1247
|
+
* Devolve a Response com o corpo ainda não lido: quem chama faz o streaming para o disco.
|
|
1248
|
+
*/
|
|
1249
|
+
async fetchFile(url, signal) {
|
|
1250
|
+
const response = await fetchWithRetry(() => fetch(url, { redirect: "error", method: "GET", signal }), {
|
|
1251
|
+
delays: FILE_RETRY_DELAYS_MS,
|
|
1252
|
+
logger: this.logger,
|
|
1253
|
+
label: "download de arquivo",
|
|
1254
|
+
signal
|
|
1255
|
+
});
|
|
1256
|
+
if (!response.ok) {
|
|
1257
|
+
throw new TasksApiError(`download de arquivo falhou (HTTP ${response.status})`, response.status);
|
|
1258
|
+
}
|
|
1259
|
+
return response;
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Confirma o download: o servidor apaga o objeto do S3 na hora. Idempotente (204);
|
|
1263
|
+
* 404/410 também significam "não há mais nada a apagar" e contam como confirmado.
|
|
1264
|
+
*/
|
|
1265
|
+
async confirmFileDownload(runId, fileId) {
|
|
1266
|
+
const response = await fetchWithRetry(
|
|
1267
|
+
() => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/files/${encodeURIComponent(fileId)}`, {
|
|
1268
|
+
redirect: "error",
|
|
1269
|
+
method: "DELETE",
|
|
1270
|
+
headers: this.headers(),
|
|
1271
|
+
signal: AbortSignal.timeout(UPDATE_TIMEOUT_MS)
|
|
1272
|
+
}),
|
|
1273
|
+
{ delays: FILE_RETRY_DELAYS_MS, logger: this.logger, label: `confirma\xE7\xE3o do arquivo ${fileId}` }
|
|
1274
|
+
);
|
|
1275
|
+
if (!response.ok && response.status !== 404 && response.status !== 410) {
|
|
1276
|
+
throw new TasksApiError(`confirma\xE7\xE3o de download rejeitada (HTTP ${response.status})`, response.status);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1142
1279
|
};
|
|
1143
1280
|
|
|
1144
1281
|
// src/tasks/backoff.ts
|
|
@@ -1164,7 +1301,7 @@ var Backoff = class {
|
|
|
1164
1301
|
this.authFailures++;
|
|
1165
1302
|
if (this.authFailures === 1 || this.authFailures % 10 === 0) {
|
|
1166
1303
|
this.logger.error(
|
|
1167
|
-
error.status === 402 ? "agente fora do limite do plano (402) \u2014 fa\xE7a upgrade em Plano e cobran\xE7a para reativ\xE1-lo" : "chave rejeitada (401) \u2014 verifique o Client Secret
|
|
1304
|
+
error.status === 402 ? "agente fora do limite do plano (402) \u2014 fa\xE7a upgrade em Plano e cobran\xE7a para reativ\xE1-lo" : "chave rejeitada (401) \u2014 verifique o Client Secret na Central: abra o agente \u2192 Configura\xE7\xF5es \u2192 agente \u2192 aba credenciais"
|
|
1168
1305
|
);
|
|
1169
1306
|
}
|
|
1170
1307
|
return Math.max(this.floorMs, AUTH_FAILURE_INTERVAL_MS);
|
|
@@ -1190,6 +1327,9 @@ var TERMINAL_ERROR_NAMES = /* @__PURE__ */ new Set(["RunCanceled", "AbortError",
|
|
|
1190
1327
|
function isTerminalError(error) {
|
|
1191
1328
|
return error instanceof Error && TERMINAL_ERROR_NAMES.has(error.name);
|
|
1192
1329
|
}
|
|
1330
|
+
function isCancellation(error) {
|
|
1331
|
+
return error instanceof Error && (error.name === "RunCanceled" || error.name === "AbortError");
|
|
1332
|
+
}
|
|
1193
1333
|
|
|
1194
1334
|
// src/tasks/each.ts
|
|
1195
1335
|
function thrownMessage(error) {
|
|
@@ -1255,16 +1395,15 @@ async function eachItem(list, handler, options, sink) {
|
|
|
1255
1395
|
if (!handle.reported) handle.succeeded();
|
|
1256
1396
|
} catch (error) {
|
|
1257
1397
|
if (isTerminalError(error) || sink.isAborted()) {
|
|
1258
|
-
step?.setStatus("cancelled");
|
|
1259
|
-
step?.finish();
|
|
1398
|
+
step?.setStatus("cancelled").succeeded();
|
|
1260
1399
|
throw error;
|
|
1261
1400
|
}
|
|
1262
1401
|
const message = thrownMessage(error);
|
|
1263
1402
|
if (!handle.reported) handle.failed(message);
|
|
1264
|
-
step?.
|
|
1403
|
+
step?.failed(error);
|
|
1265
1404
|
sink.logError(`item ${description} falhou: ${message}`);
|
|
1266
1405
|
}
|
|
1267
|
-
step?.
|
|
1406
|
+
step?.succeeded();
|
|
1268
1407
|
if (options.progress !== false) sink.advance();
|
|
1269
1408
|
result.total += 1;
|
|
1270
1409
|
result[handle.status ?? "succeeded"] += 1;
|
|
@@ -1273,6 +1412,255 @@ async function eachItem(list, handler, options, sink) {
|
|
|
1273
1412
|
return result;
|
|
1274
1413
|
}
|
|
1275
1414
|
|
|
1415
|
+
// src/tasks/files.ts
|
|
1416
|
+
var LOCAL_FILES_DIR_ENV = "HOBOTS_LOCAL_FILES_DIR";
|
|
1417
|
+
var FILE_DOWNLOAD_IDLE_TIMEOUT_MS = 6e4;
|
|
1418
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1419
|
+
function isFileRef(value) {
|
|
1420
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1421
|
+
const ref = value;
|
|
1422
|
+
return typeof ref.id === "string" && typeof ref.name === "string" && typeof ref.size === "number" && typeof ref.contentType === "string" && (ref.storage === "s3" || ref.storage === "local");
|
|
1423
|
+
}
|
|
1424
|
+
function fileField(params, field) {
|
|
1425
|
+
if (!Object.prototype.hasOwnProperty.call(params, field)) return void 0;
|
|
1426
|
+
const value = params[field];
|
|
1427
|
+
if (isFileRef(value)) return value;
|
|
1428
|
+
if (Array.isArray(value) && value.every(isFileRef)) return value;
|
|
1429
|
+
return void 0;
|
|
1430
|
+
}
|
|
1431
|
+
function collectFileRefs(params) {
|
|
1432
|
+
const refs = [];
|
|
1433
|
+
for (const field of Object.keys(params)) {
|
|
1434
|
+
const value = fileField(params, field);
|
|
1435
|
+
if (value !== void 0) refs.push(...Array.isArray(value) ? value : [value]);
|
|
1436
|
+
}
|
|
1437
|
+
return refs;
|
|
1438
|
+
}
|
|
1439
|
+
function safeFileName(name, fallback) {
|
|
1440
|
+
const cleaned = name.replace(/^.*[\\/]/, "").replace(/[\u0000-\u001f<>:"|?*]/g, "_").trim();
|
|
1441
|
+
return cleaned === "" || cleaned === "." || cleaned === ".." ? fallback : cleaned;
|
|
1442
|
+
}
|
|
1443
|
+
function isInsideDir(root, candidate, sep) {
|
|
1444
|
+
const base = root.endsWith(sep) ? root : root + sep;
|
|
1445
|
+
return candidate.startsWith(base) && candidate.length > base.length;
|
|
1446
|
+
}
|
|
1447
|
+
function unavailableLocal(ref) {
|
|
1448
|
+
return new Error(`arquivo "${ref.name}" enviado pelo Studio de outro computador ou j\xE1 consumido`);
|
|
1449
|
+
}
|
|
1450
|
+
function errorCode(error) {
|
|
1451
|
+
return error?.code;
|
|
1452
|
+
}
|
|
1453
|
+
var RunFileStore = class {
|
|
1454
|
+
constructor(deps) {
|
|
1455
|
+
this.deps = deps;
|
|
1456
|
+
this.files = {
|
|
1457
|
+
list: () => collectFileRefs(deps.params),
|
|
1458
|
+
get: (field) => fileField(deps.params, field),
|
|
1459
|
+
// async: erro de uso (campo sem arquivo, valor inválido) chega como Promise rejeitada, nunca síncrono
|
|
1460
|
+
download: async (refOrField, options) => this.download(this.single(refOrField), options?.dest),
|
|
1461
|
+
downloadAll: async (field, options) => this.downloadAll(field, options?.dest),
|
|
1462
|
+
read: async (refOrField) => this.read(this.single(refOrField))
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
deps;
|
|
1466
|
+
files;
|
|
1467
|
+
tempDir;
|
|
1468
|
+
/** Apaga a pasta temporária da execução (cópias que o bot não levou para `dest`). Nunca lança. */
|
|
1469
|
+
async cleanup() {
|
|
1470
|
+
const pending = this.tempDir;
|
|
1471
|
+
this.tempDir = void 0;
|
|
1472
|
+
if (!pending) return;
|
|
1473
|
+
try {
|
|
1474
|
+
const { rm } = await import("fs/promises");
|
|
1475
|
+
await rm(await pending, { recursive: true, force: true });
|
|
1476
|
+
} catch {
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
field(field) {
|
|
1480
|
+
const value = fileField(this.deps.params, field);
|
|
1481
|
+
if (value === void 0) throw new Error(`campo "${field}" n\xE3o tem arquivo nos params`);
|
|
1482
|
+
return value;
|
|
1483
|
+
}
|
|
1484
|
+
single(refOrField) {
|
|
1485
|
+
if (typeof refOrField === "string") {
|
|
1486
|
+
const value = this.field(refOrField);
|
|
1487
|
+
if (Array.isArray(value)) {
|
|
1488
|
+
throw new Error(`campo "${refOrField}" tem v\xE1rios arquivos \u2014 use downloadAll("${refOrField}") ou passe um item da lista`);
|
|
1489
|
+
}
|
|
1490
|
+
return value;
|
|
1491
|
+
}
|
|
1492
|
+
if (!isFileRef(refOrField)) throw new TypeError("esperado um FileRef ou o nome de um campo de arquivo");
|
|
1493
|
+
return refOrField;
|
|
1494
|
+
}
|
|
1495
|
+
async downloadAll(field, dest) {
|
|
1496
|
+
const value = this.field(field);
|
|
1497
|
+
const refs = Array.isArray(value) ? value : [value];
|
|
1498
|
+
const paths = [];
|
|
1499
|
+
for (const ref of refs) paths.push(await this.download(ref, dest, true));
|
|
1500
|
+
return paths;
|
|
1501
|
+
}
|
|
1502
|
+
async read(ref) {
|
|
1503
|
+
const { readFile, rm } = await import("fs/promises");
|
|
1504
|
+
const { dirname } = await import("path");
|
|
1505
|
+
const path = await this.download(ref, void 0);
|
|
1506
|
+
try {
|
|
1507
|
+
return await readFile(path);
|
|
1508
|
+
} finally {
|
|
1509
|
+
await rm(dirname(path), { recursive: true, force: true });
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
async download(ref, dest, destIsDir = false) {
|
|
1513
|
+
this.checkCanceled();
|
|
1514
|
+
const target = await this.resolveTarget(ref, dest, destIsDir);
|
|
1515
|
+
if (ref.storage === "local") await this.copyLocal(ref, target);
|
|
1516
|
+
else await this.downloadS3(ref, target);
|
|
1517
|
+
return target;
|
|
1518
|
+
}
|
|
1519
|
+
checkCanceled() {
|
|
1520
|
+
if (this.deps.signal.aborted) throw runCanceledError();
|
|
1521
|
+
}
|
|
1522
|
+
runTempDir() {
|
|
1523
|
+
this.tempDir ??= (async () => {
|
|
1524
|
+
const { mkdtemp } = await import("fs/promises");
|
|
1525
|
+
const { tmpdir } = await import("os");
|
|
1526
|
+
const { join } = await import("path");
|
|
1527
|
+
return mkdtemp(join(tmpdir(), "hobots-files-"));
|
|
1528
|
+
})();
|
|
1529
|
+
return this.tempDir;
|
|
1530
|
+
}
|
|
1531
|
+
async resolveTarget(ref, dest, destIsDir) {
|
|
1532
|
+
const { mkdir, mkdtemp, stat } = await import("fs/promises");
|
|
1533
|
+
const { dirname, join, resolve: resolve2 } = await import("path");
|
|
1534
|
+
const name = safeFileName(ref.name, "arquivo");
|
|
1535
|
+
if (dest === void 0) {
|
|
1536
|
+
return join(await mkdtemp(join(await this.runTempDir(), "f-")), name);
|
|
1537
|
+
}
|
|
1538
|
+
const isDir = destIsDir || dest.endsWith("/") || dest.endsWith("\\") || (await stat(dest).catch(() => void 0))?.isDirectory() === true;
|
|
1539
|
+
if (isDir) {
|
|
1540
|
+
await mkdir(dest, { recursive: true });
|
|
1541
|
+
return resolve2(dest, name);
|
|
1542
|
+
}
|
|
1543
|
+
await mkdir(dirname(resolve2(dest)), { recursive: true });
|
|
1544
|
+
return resolve2(dest);
|
|
1545
|
+
}
|
|
1546
|
+
/** storage 's3': URL pré-assinada → GET em streaming → confere o tamanho → só então DELETE. */
|
|
1547
|
+
async downloadS3(ref, target) {
|
|
1548
|
+
const { api, runId, signal } = this.deps;
|
|
1549
|
+
let expected;
|
|
1550
|
+
let url;
|
|
1551
|
+
try {
|
|
1552
|
+
const info = await api.requestFileDownload(runId, ref.id, signal);
|
|
1553
|
+
expected = info.size >= 0 ? info.size : ref.size;
|
|
1554
|
+
url = info.url;
|
|
1555
|
+
} catch (error) {
|
|
1556
|
+
throw this.translate(error, ref);
|
|
1557
|
+
}
|
|
1558
|
+
const { open, rename, rm } = await import("fs/promises");
|
|
1559
|
+
const part = `${target}.part`;
|
|
1560
|
+
const idleTimeoutMs = this.deps.idleTimeoutMs ?? FILE_DOWNLOAD_IDLE_TIMEOUT_MS;
|
|
1561
|
+
const controller = new AbortController();
|
|
1562
|
+
let timedOut = false;
|
|
1563
|
+
let timer;
|
|
1564
|
+
const arm = () => {
|
|
1565
|
+
clearTimeout(timer);
|
|
1566
|
+
timer = setTimeout(() => {
|
|
1567
|
+
timedOut = true;
|
|
1568
|
+
controller.abort();
|
|
1569
|
+
}, idleTimeoutMs);
|
|
1570
|
+
};
|
|
1571
|
+
const onCancel = () => controller.abort();
|
|
1572
|
+
signal.addEventListener("abort", onCancel, { once: true });
|
|
1573
|
+
try {
|
|
1574
|
+
arm();
|
|
1575
|
+
const response = await api.fetchFile(url, controller.signal);
|
|
1576
|
+
const handle = await open(part, "w");
|
|
1577
|
+
let written = 0;
|
|
1578
|
+
try {
|
|
1579
|
+
const reader = response.body?.getReader();
|
|
1580
|
+
const stopReading = () => void reader?.cancel().catch(() => void 0);
|
|
1581
|
+
controller.signal.addEventListener("abort", stopReading, { once: true });
|
|
1582
|
+
for (; ; ) {
|
|
1583
|
+
const chunk = reader ? await reader.read() : { done: true, value: void 0 };
|
|
1584
|
+
if (chunk.done) break;
|
|
1585
|
+
arm();
|
|
1586
|
+
written += chunk.value.byteLength;
|
|
1587
|
+
if (written > expected) {
|
|
1588
|
+
stopReading();
|
|
1589
|
+
break;
|
|
1590
|
+
}
|
|
1591
|
+
await handle.write(chunk.value);
|
|
1592
|
+
}
|
|
1593
|
+
} finally {
|
|
1594
|
+
await handle.close();
|
|
1595
|
+
}
|
|
1596
|
+
if (written !== expected) {
|
|
1597
|
+
throw new Error(`tamanho divergente no download de "${ref.name}": recebidos ${written} bytes, esperados ${expected}`);
|
|
1598
|
+
}
|
|
1599
|
+
await rename(part, target);
|
|
1600
|
+
} catch (error) {
|
|
1601
|
+
await rm(part, { force: true });
|
|
1602
|
+
if (signal.aborted) throw runCanceledError();
|
|
1603
|
+
if (timedOut) {
|
|
1604
|
+
throw new Error(`download de "${ref.name}" parado h\xE1 ${idleTimeoutMs / 1e3}s \u2014 tente de novo`);
|
|
1605
|
+
}
|
|
1606
|
+
throw this.translate(error, ref);
|
|
1607
|
+
} finally {
|
|
1608
|
+
clearTimeout(timer);
|
|
1609
|
+
signal.removeEventListener("abort", onCancel);
|
|
1610
|
+
}
|
|
1611
|
+
try {
|
|
1612
|
+
await api.confirmFileDownload(runId, ref.id);
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
this.deps.warn(`arquivo "${ref.name}" baixado, mas a confirma\xE7\xE3o ao servidor falhou: ${String(error)}`);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
/** storage 'local': copia o único arquivo de `$HOBOTS_LOCAL_FILES_DIR/<id>/` e apaga a pasta de origem. */
|
|
1618
|
+
async copyLocal(ref, target) {
|
|
1619
|
+
const base = process.env[LOCAL_FILES_DIR_ENV];
|
|
1620
|
+
if (!base) throw unavailableLocal(ref);
|
|
1621
|
+
const { copyFile, readdir, rename, rm, stat } = await import("fs/promises");
|
|
1622
|
+
const { join, resolve: resolve2, sep } = await import("path");
|
|
1623
|
+
const root = resolve2(base);
|
|
1624
|
+
const folder = resolve2(root, ref.id);
|
|
1625
|
+
if (!isInsideDir(root, folder, sep) || !UUID_RE.test(ref.id)) {
|
|
1626
|
+
throw new Error(`id de arquivo local inv\xE1lido: ${JSON.stringify(ref.id)}`);
|
|
1627
|
+
}
|
|
1628
|
+
let names;
|
|
1629
|
+
try {
|
|
1630
|
+
names = (await readdir(folder, { withFileTypes: true })).filter((entry) => entry.isFile()).map((entry) => entry.name);
|
|
1631
|
+
} catch (error) {
|
|
1632
|
+
if (errorCode(error) === "ENOENT" || errorCode(error) === "ENOTDIR") throw unavailableLocal(ref);
|
|
1633
|
+
throw error;
|
|
1634
|
+
}
|
|
1635
|
+
if (names.length === 0) throw unavailableLocal(ref);
|
|
1636
|
+
if (names.length > 1) throw new Error(`pasta do arquivo "${ref.name}" tem ${names.length} arquivos \u2014 esperado 1`);
|
|
1637
|
+
this.checkCanceled();
|
|
1638
|
+
const part = `${target}.part`;
|
|
1639
|
+
try {
|
|
1640
|
+
await copyFile(join(folder, names[0]), part);
|
|
1641
|
+
const { size } = await stat(part);
|
|
1642
|
+
if (size !== ref.size) {
|
|
1643
|
+
throw new Error(`tamanho divergente na c\xF3pia de "${ref.name}": copiados ${size} bytes, esperados ${ref.size}`);
|
|
1644
|
+
}
|
|
1645
|
+
await rename(part, target);
|
|
1646
|
+
} catch (error) {
|
|
1647
|
+
await rm(part, { force: true });
|
|
1648
|
+
throw error;
|
|
1649
|
+
}
|
|
1650
|
+
await rm(folder, { recursive: true, force: true });
|
|
1651
|
+
}
|
|
1652
|
+
translate(error, ref) {
|
|
1653
|
+
if (this.deps.signal.aborted) return runCanceledError();
|
|
1654
|
+
if (error instanceof TasksApiError && error.status === 410) {
|
|
1655
|
+
return new TasksApiError(`arquivo "${ref.name}" j\xE1 baixado ou expirado`, 410);
|
|
1656
|
+
}
|
|
1657
|
+
if (error instanceof TasksApiError && error.status === 404) {
|
|
1658
|
+
return new TasksApiError(`arquivo "${ref.name}" n\xE3o encontrado nesta execu\xE7\xE3o`, 404);
|
|
1659
|
+
}
|
|
1660
|
+
return error;
|
|
1661
|
+
}
|
|
1662
|
+
};
|
|
1663
|
+
|
|
1276
1664
|
// src/retry.ts
|
|
1277
1665
|
var DEFAULT_RETRIES = 3;
|
|
1278
1666
|
var DEFAULT_DELAY_MS = 1e3;
|
|
@@ -1457,7 +1845,7 @@ var RunReporter = class {
|
|
|
1457
1845
|
tx.setStepGuard(checkCanceled);
|
|
1458
1846
|
const eachSink = {
|
|
1459
1847
|
report: (status, message, payload, id, image3) => this.addItem(status, message, payload, id, image3),
|
|
1460
|
-
startStep: (op, description) => tx.
|
|
1848
|
+
startStep: (op, description) => tx.start(op, description),
|
|
1461
1849
|
declareTotal: (total) => {
|
|
1462
1850
|
if (this.progressTotal == null) this.setProgress(this.progressCurrent, total);
|
|
1463
1851
|
},
|
|
@@ -1511,12 +1899,14 @@ var RunReporter = class {
|
|
|
1511
1899
|
);
|
|
1512
1900
|
const signal = this.abortController.signal;
|
|
1513
1901
|
const sleep2 = (ms) => cancelableSleep(ms, signal);
|
|
1902
|
+
this.fileStore = new RunFileStore({ api, runId, params, signal, warn: (message) => this.log("warning", message) });
|
|
1514
1903
|
this.context = {
|
|
1515
1904
|
runId,
|
|
1516
1905
|
params,
|
|
1517
1906
|
log,
|
|
1518
1907
|
items,
|
|
1519
1908
|
progress,
|
|
1909
|
+
files: this.fileStore.files,
|
|
1520
1910
|
transaction: tx,
|
|
1521
1911
|
flush: (timeoutMs) => this.flush(timeoutMs),
|
|
1522
1912
|
signal,
|
|
@@ -1548,8 +1938,17 @@ var RunReporter = class {
|
|
|
1548
1938
|
itemSeq = 0;
|
|
1549
1939
|
/** Servidor sem o recurso (404) — para de tentar (e de avisar) nesta run. */
|
|
1550
1940
|
itemsUnsupported = false;
|
|
1551
|
-
/**
|
|
1941
|
+
/**
|
|
1942
|
+
* Cota de itens por run do servidor (429, legado — hoje a api não tem teto de itens) —
|
|
1943
|
+
* degrada com um aviso só.
|
|
1944
|
+
*/
|
|
1552
1945
|
itemsQuotaExceeded = false;
|
|
1946
|
+
/**
|
|
1947
|
+
* Armazenamento do plano cheio (402 `storage_limit`): o lote é descartado e o aviso sai
|
|
1948
|
+
* uma vez por episódio. Os envios continuam — num agente `start()` o espaço pode ser
|
|
1949
|
+
* liberado (ou o plano trocado) sem reiniciar; um lote aceito rearma o aviso.
|
|
1950
|
+
*/
|
|
1951
|
+
itemsStorageFull = false;
|
|
1553
1952
|
/**
|
|
1554
1953
|
* Só o kill() do exit path fecha os itens — `dead` (run cancelada/finalizada no
|
|
1555
1954
|
* servidor) NÃO fecha: o servidor aceita itens de run terminal (trabalho já feito).
|
|
@@ -1571,6 +1970,12 @@ var RunReporter = class {
|
|
|
1571
1970
|
progressDirty = false;
|
|
1572
1971
|
progressTimer;
|
|
1573
1972
|
progressWarned = false;
|
|
1973
|
+
/** Arquivos do formulário desta run (`ctx.files`) — a pasta temporária é apagada no fim. */
|
|
1974
|
+
fileStore;
|
|
1975
|
+
/** Apaga as cópias de arquivo que ficaram na pasta temporária da run. Nunca lança. */
|
|
1976
|
+
cleanupFiles() {
|
|
1977
|
+
return this.fileStore.cleanup();
|
|
1978
|
+
}
|
|
1574
1979
|
get isDead() {
|
|
1575
1980
|
return this.dead;
|
|
1576
1981
|
}
|
|
@@ -1853,6 +2258,7 @@ var RunReporter = class {
|
|
|
1853
2258
|
if (this.itemsUnsupported) return;
|
|
1854
2259
|
try {
|
|
1855
2260
|
await this.api.createItems(this.runId, { items: batch });
|
|
2261
|
+
this.itemsStorageFull = false;
|
|
1856
2262
|
} catch (error) {
|
|
1857
2263
|
if (error instanceof TasksApiError && error.status === 404) {
|
|
1858
2264
|
this.itemsUnsupported = true;
|
|
@@ -1866,6 +2272,13 @@ var RunReporter = class {
|
|
|
1866
2272
|
}
|
|
1867
2273
|
return;
|
|
1868
2274
|
}
|
|
2275
|
+
if (error instanceof TasksApiError && error.status === 402) {
|
|
2276
|
+
if (!this.itemsStorageFull) {
|
|
2277
|
+
this.itemsStorageFull = true;
|
|
2278
|
+
this.log("warning", "itens descartados: armazenamento do plano cheio \u2014 libere espa\xE7o ou fa\xE7a upgrade em Plano e cobran\xE7a");
|
|
2279
|
+
}
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
1869
2282
|
if (error instanceof TasksApiError) {
|
|
1870
2283
|
this.logger.error(`lote de ${batch.length} item(ns) rejeitado pelo servidor:`, error);
|
|
1871
2284
|
this.log("warning", `${batch.length} item(ns) descartado(s): envio rejeitado pelo servidor (HTTP ${error.status})`);
|
|
@@ -2150,16 +2563,19 @@ var Step = class {
|
|
|
2150
2563
|
return this;
|
|
2151
2564
|
}
|
|
2152
2565
|
/**
|
|
2153
|
-
* Marca a step como `error` guardando o motivo em `data.error` ({ type, message })
|
|
2154
|
-
* Aceita qualquer valor lançado; `message` sobrescreve a mensagem do erro.
|
|
2566
|
+
* Marca a step como `error` guardando o motivo em `data.error` ({ type, message }) e a
|
|
2567
|
+
* fecha. Aceita qualquer valor lançado; `message` sobrescreve a mensagem do erro.
|
|
2155
2568
|
* `options.image` pendura um print do estado da tela em `data.attachments`.
|
|
2569
|
+
* Numa step já fechada não faz nada.
|
|
2156
2570
|
*/
|
|
2157
|
-
|
|
2571
|
+
failed(error, message, options) {
|
|
2572
|
+
if (this.endTimestamp) return this;
|
|
2158
2573
|
this.status = "error";
|
|
2159
2574
|
const type = error instanceof Error ? error.name : typeof error === "object" && error !== null ? "Error" : void 0;
|
|
2160
2575
|
const raw = message ?? (error instanceof Error ? error.message || error.name : String(error));
|
|
2161
2576
|
this.setData("error", { ...type ? { type } : {}, message: stepMessage(raw) });
|
|
2162
2577
|
if (options?.image !== void 0) this.attachImage(options.image);
|
|
2578
|
+
this.close();
|
|
2163
2579
|
return this;
|
|
2164
2580
|
}
|
|
2165
2581
|
/**
|
|
@@ -2174,7 +2590,7 @@ var Step = class {
|
|
|
2174
2590
|
if (!id) return;
|
|
2175
2591
|
this.setData("attachments", [...current, id]);
|
|
2176
2592
|
}
|
|
2177
|
-
/** Motivo registrado via setStatus(…, message) ou
|
|
2593
|
+
/** Motivo registrado via setStatus(…, message) ou failed — usado no log de FINISH. */
|
|
2178
2594
|
reason() {
|
|
2179
2595
|
const error = this.data?.error;
|
|
2180
2596
|
const text = typeof error?.message === "string" ? error.message : this.data?.message;
|
|
@@ -2185,10 +2601,18 @@ var Step = class {
|
|
|
2185
2601
|
return this;
|
|
2186
2602
|
}
|
|
2187
2603
|
/** Step aninhada (filha desta step). */
|
|
2188
|
-
|
|
2604
|
+
start(op, description) {
|
|
2189
2605
|
return this.transaction.createStep(this.stepId, op, description);
|
|
2190
2606
|
}
|
|
2191
|
-
|
|
2607
|
+
/**
|
|
2608
|
+
* Fecha a step. O status fica o que estiver (`ok` por padrão, ou o de `setStatus`).
|
|
2609
|
+
* Idempotente; a transaction fecha no fim da run o que ficou aberto.
|
|
2610
|
+
*/
|
|
2611
|
+
succeeded() {
|
|
2612
|
+
this.close();
|
|
2613
|
+
return this;
|
|
2614
|
+
}
|
|
2615
|
+
close() {
|
|
2192
2616
|
if (this.endTimestamp) return;
|
|
2193
2617
|
this.endTimestamp = nowISO();
|
|
2194
2618
|
this.transaction.emit(
|
|
@@ -2281,7 +2705,7 @@ var Transaction = class {
|
|
|
2281
2705
|
}
|
|
2282
2706
|
/**
|
|
2283
2707
|
* Conecta a checagem de cancelamento chamada antes de cada step nova — é assim que
|
|
2284
|
-
* `hobots.transaction.
|
|
2708
|
+
* `hobots.transaction.start()` (e `step.start()`) param uma run cancelada.
|
|
2285
2709
|
*/
|
|
2286
2710
|
setStepGuard(guard) {
|
|
2287
2711
|
this.stepGuard = guard;
|
|
@@ -2293,10 +2717,32 @@ var Transaction = class {
|
|
|
2293
2717
|
this.emit("step", "start", stepLabel(op, description));
|
|
2294
2718
|
return step;
|
|
2295
2719
|
}
|
|
2296
|
-
/** Step direta da transaction. */
|
|
2297
|
-
|
|
2720
|
+
/** Step direta da transaction. `op` é o nome usado por `succeeded(name)`/`failed(error, name)`. */
|
|
2721
|
+
start(op, description) {
|
|
2298
2722
|
return this.createStep(this.stepId, op, description);
|
|
2299
2723
|
}
|
|
2724
|
+
/**
|
|
2725
|
+
* Fecha a step aberta mais recente — com `name`, a mais recente aberta com esse `op`.
|
|
2726
|
+
* Sem step aberta (ou sem nenhuma com esse nome), não faz nada e devolve undefined.
|
|
2727
|
+
*/
|
|
2728
|
+
succeeded(name) {
|
|
2729
|
+
return this.findOpenStep(name)?.succeeded();
|
|
2730
|
+
}
|
|
2731
|
+
/**
|
|
2732
|
+
* Marca como `error` e fecha a step aberta mais recente — com `name`, a mais recente
|
|
2733
|
+
* aberta com esse `op`. `options.message` sobrescreve a mensagem do erro e
|
|
2734
|
+
* `options.image` pendura um print. Sem step aberta, não faz nada e devolve undefined.
|
|
2735
|
+
*/
|
|
2736
|
+
failed(error, name, options) {
|
|
2737
|
+
return this.findOpenStep(name)?.failed(error, options?.message, options);
|
|
2738
|
+
}
|
|
2739
|
+
findOpenStep(name) {
|
|
2740
|
+
for (let i = this.steps.length - 1; i >= 0; i--) {
|
|
2741
|
+
const step = this.steps[i];
|
|
2742
|
+
if (!step.endTimestamp && (name === void 0 || step.op === name)) return step;
|
|
2743
|
+
}
|
|
2744
|
+
return void 0;
|
|
2745
|
+
}
|
|
2300
2746
|
setTag(key, value) {
|
|
2301
2747
|
this.tags[key] = value;
|
|
2302
2748
|
return this;
|
|
@@ -2334,7 +2780,7 @@ var Transaction = class {
|
|
|
2334
2780
|
remaining.push(step);
|
|
2335
2781
|
}
|
|
2336
2782
|
}
|
|
2337
|
-
for (const step of batch) step.
|
|
2783
|
+
for (const step of batch) step.succeeded();
|
|
2338
2784
|
this.steps.splice(0, this.steps.length, ...remaining);
|
|
2339
2785
|
if (this.client) {
|
|
2340
2786
|
for (let i = 0; i < batch.length; i += MAX_STEPS) {
|
|
@@ -2366,7 +2812,7 @@ var Transaction = class {
|
|
|
2366
2812
|
finish() {
|
|
2367
2813
|
if (this.finished) return;
|
|
2368
2814
|
this.finished = true;
|
|
2369
|
-
for (const step of this.steps) step.
|
|
2815
|
+
for (const step of this.steps) step.succeeded();
|
|
2370
2816
|
const endTimestamp = nowISO();
|
|
2371
2817
|
this.emit(
|
|
2372
2818
|
"transaction",
|
|
@@ -2428,14 +2874,21 @@ async function executeRun(input) {
|
|
|
2428
2874
|
await reporter.finalizeItems();
|
|
2429
2875
|
tx.setStatus("error");
|
|
2430
2876
|
tx.finish();
|
|
2877
|
+
captureRunFailure(client, error, reporter);
|
|
2431
2878
|
reporter.reportFailure(toRunError(error));
|
|
2432
2879
|
logger.debug(`run ${run.id} falhou: ${String(error)}`);
|
|
2433
2880
|
}
|
|
2434
2881
|
await reporter.finalize();
|
|
2435
2882
|
} finally {
|
|
2883
|
+
await reporter.cleanupFiles();
|
|
2436
2884
|
tracker.remove(reporter);
|
|
2437
2885
|
}
|
|
2438
2886
|
}
|
|
2887
|
+
function captureRunFailure(client, error, reporter) {
|
|
2888
|
+
if (!client?.options.captureRunErrors) return;
|
|
2889
|
+
if (reporter.isDead || isCancellation(error) || wasCaptured(error)) return;
|
|
2890
|
+
client.captureException(error, { type: "error", tags: { mechanism: "run_handler", run_id: reporter.runId } });
|
|
2891
|
+
}
|
|
2439
2892
|
function warnIncompleteProgress(reporter) {
|
|
2440
2893
|
const snapshot = reporter.context.progress.snapshot;
|
|
2441
2894
|
if (!snapshot || snapshot.current >= snapshot.total) return;
|
|
@@ -2627,6 +3080,86 @@ var TaskAgent = class {
|
|
|
2627
3080
|
}
|
|
2628
3081
|
};
|
|
2629
3082
|
|
|
3083
|
+
// src/tasks/attached.ts
|
|
3084
|
+
var START_RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
|
|
3085
|
+
function isTransient(error) {
|
|
3086
|
+
if (!(error instanceof TasksApiError)) return true;
|
|
3087
|
+
return error.status >= 500 || error.status === 429;
|
|
3088
|
+
}
|
|
3089
|
+
function describeRejection(runId, error) {
|
|
3090
|
+
if (error instanceof TasksApiError && error.status === 404) {
|
|
3091
|
+
return `run ${runId} n\xE3o existe na plataforma (404) \u2014 nada a executar`;
|
|
3092
|
+
}
|
|
3093
|
+
if (error instanceof TasksApiError && error.status === 409) {
|
|
3094
|
+
return `run ${runId} n\xE3o pode ser iniciada (409: cancelada, j\xE1 iniciada ou de outra inst\xE2ncia) \u2014 nada a executar`;
|
|
3095
|
+
}
|
|
3096
|
+
return `n\xE3o foi poss\xEDvel iniciar a run ${runId} (${String(error)}) \u2014 nada a executar`;
|
|
3097
|
+
}
|
|
3098
|
+
var AttachedRunner = class {
|
|
3099
|
+
constructor(runId, handler, deps) {
|
|
3100
|
+
this.runId = runId;
|
|
3101
|
+
this.handler = handler;
|
|
3102
|
+
this.deps = deps;
|
|
3103
|
+
this.done = new Promise((resolve2) => {
|
|
3104
|
+
this.resolveDone = resolve2;
|
|
3105
|
+
});
|
|
3106
|
+
}
|
|
3107
|
+
runId;
|
|
3108
|
+
handler;
|
|
3109
|
+
deps;
|
|
3110
|
+
abort = new AbortController();
|
|
3111
|
+
/** Resolve quando a run terminou (ou não pôde ser iniciada, ou o runner foi parado antes). */
|
|
3112
|
+
done;
|
|
3113
|
+
resolveDone;
|
|
3114
|
+
async run() {
|
|
3115
|
+
try {
|
|
3116
|
+
const run = await this.startRun();
|
|
3117
|
+
if (!run) return;
|
|
3118
|
+
this.deps.logger.debug(`run ${run.id} iniciada (HOBOTS_RUN_ID)`);
|
|
3119
|
+
await executeRun({
|
|
3120
|
+
run,
|
|
3121
|
+
handler: this.handler,
|
|
3122
|
+
api: this.deps.api,
|
|
3123
|
+
client: this.deps.client,
|
|
3124
|
+
logger: this.deps.logger,
|
|
3125
|
+
tracker: this.deps.tracker
|
|
3126
|
+
});
|
|
3127
|
+
} catch (error) {
|
|
3128
|
+
this.deps.logger.error("erro inesperado na execu\xE7\xE3o do agente:", error);
|
|
3129
|
+
} finally {
|
|
3130
|
+
this.resolveDone();
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
/** Desiste de iniciar a run (close() antes da plataforma responder). Uma run já iniciada segue pelo tracker. */
|
|
3134
|
+
stop() {
|
|
3135
|
+
this.abort.abort();
|
|
3136
|
+
}
|
|
3137
|
+
async startRun() {
|
|
3138
|
+
for (let attempt = 0; ; attempt++) {
|
|
3139
|
+
if (this.abort.signal.aborted) return void 0;
|
|
3140
|
+
try {
|
|
3141
|
+
return await this.deps.api.startRun(this.runId, { instance: this.deps.instanceInfo() }, this.abort.signal);
|
|
3142
|
+
} catch (error) {
|
|
3143
|
+
if (this.abort.signal.aborted) return void 0;
|
|
3144
|
+
if (!isTransient(error)) {
|
|
3145
|
+
this.deps.logger.error(describeRejection(this.runId, error));
|
|
3146
|
+
return void 0;
|
|
3147
|
+
}
|
|
3148
|
+
const delay = START_RETRY_DELAYS_MS[attempt];
|
|
3149
|
+
if (delay === void 0) {
|
|
3150
|
+
this.deps.logger.error(
|
|
3151
|
+
`n\xE3o foi poss\xEDvel iniciar a run ${this.runId} ap\xF3s ${attempt + 1} tentativas (${String(error)}) \u2014 desistindo`
|
|
3152
|
+
);
|
|
3153
|
+
return void 0;
|
|
3154
|
+
}
|
|
3155
|
+
const wait = jitter(delay);
|
|
3156
|
+
this.deps.logger.warn(`in\xEDcio da run ${this.runId} falhou (${String(error)}), pr\xF3xima tentativa em ${wait}ms`);
|
|
3157
|
+
await abortableSleep(wait, this.abort.signal);
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
};
|
|
3162
|
+
|
|
2630
3163
|
// src/tasks/item-columns.ts
|
|
2631
3164
|
var KEY_RE = /^[a-zA-Z0-9_.-]{1,100}$/;
|
|
2632
3165
|
var MAX_COLUMNS = WIRE_LIMITS.MAX_ITEM_COLUMNS;
|
|
@@ -2832,11 +3365,18 @@ function hostname() {
|
|
|
2832
3365
|
return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
|
|
2833
3366
|
}
|
|
2834
3367
|
function createRuntime(options, handler) {
|
|
3368
|
+
loadDotenv();
|
|
2835
3369
|
const config = resolveConfig({ clientSecret: options.clientSecret });
|
|
2836
3370
|
const logger = makeLogger(options.debug ?? false);
|
|
2837
3371
|
const environment = resolveEnvironmentOption(options.environment);
|
|
2838
3372
|
const client = new Client(
|
|
2839
|
-
{
|
|
3373
|
+
{
|
|
3374
|
+
environment,
|
|
3375
|
+
release: options.release,
|
|
3376
|
+
tags: options.tags,
|
|
3377
|
+
debug: options.debug,
|
|
3378
|
+
captureRunErrors: options.captureUnhandled !== false
|
|
3379
|
+
},
|
|
2840
3380
|
config
|
|
2841
3381
|
);
|
|
2842
3382
|
const api = new TasksApi({ baseUrl: config.runsBaseUrl, key: config.key, logger });
|
|
@@ -2852,6 +3392,8 @@ function createRuntime(options, handler) {
|
|
|
2852
3392
|
heartbeat: void 0,
|
|
2853
3393
|
agent: void 0,
|
|
2854
3394
|
local: void 0,
|
|
3395
|
+
attached: void 0,
|
|
3396
|
+
runId: void 0,
|
|
2855
3397
|
instanceId: resolveInstanceId(options.instanceId),
|
|
2856
3398
|
environment,
|
|
2857
3399
|
pollIntervalMs: void 0,
|
|
@@ -2888,7 +3430,9 @@ function resolveRunOptions(options, mode, logger) {
|
|
|
2888
3430
|
}
|
|
2889
3431
|
function instanceInfo(rt) {
|
|
2890
3432
|
return {
|
|
3433
|
+
/* v8 ignore start -- defensivo: o boot define o instanceId antes de qualquer poll/criação de run */
|
|
2891
3434
|
instance_id: rt.instanceId ?? "unknown",
|
|
3435
|
+
/* v8 ignore stop */
|
|
2892
3436
|
version: SDK_VERSION,
|
|
2893
3437
|
host: hostname(),
|
|
2894
3438
|
// ambiente da instância: junto do heartbeat, é como o servidor sabe onde o robô roda
|
|
@@ -2927,6 +3471,20 @@ function launchLocal(rt) {
|
|
|
2927
3471
|
rt.local = runner;
|
|
2928
3472
|
void runner.run();
|
|
2929
3473
|
}
|
|
3474
|
+
function launchAttached(rt, runId) {
|
|
3475
|
+
const runner = new AttachedRunner(runId, rt.handler, {
|
|
3476
|
+
api: rt.api,
|
|
3477
|
+
client: rt.client,
|
|
3478
|
+
logger: rt.logger,
|
|
3479
|
+
tracker: rt.tracker,
|
|
3480
|
+
instanceInfo: () => instanceInfo(rt)
|
|
3481
|
+
});
|
|
3482
|
+
rt.attached = runner;
|
|
3483
|
+
void runner.run();
|
|
3484
|
+
void runner.done.then(() => {
|
|
3485
|
+
if (runtime === rt) void close();
|
|
3486
|
+
});
|
|
3487
|
+
}
|
|
2930
3488
|
async function boot(rt) {
|
|
2931
3489
|
if (!rt.instanceId) {
|
|
2932
3490
|
const id = await fallbackInstanceId();
|
|
@@ -2946,8 +3504,9 @@ async function boot(rt) {
|
|
|
2946
3504
|
if (rt.options.installExitHandlers !== false) {
|
|
2947
3505
|
rt.uninstallShutdown = installShutdownHandlers(rt.tracker);
|
|
2948
3506
|
}
|
|
2949
|
-
if (rt.handler.mode === "
|
|
2950
|
-
else
|
|
3507
|
+
if (rt.handler.mode === "local") launchLocal(rt);
|
|
3508
|
+
else if (rt.runId) launchAttached(rt, rt.runId);
|
|
3509
|
+
else ensureAgent(rt).start();
|
|
2951
3510
|
}
|
|
2952
3511
|
function scheduleBoot(rt) {
|
|
2953
3512
|
if (rt.bootScheduled) return;
|
|
@@ -2965,6 +3524,7 @@ function startAgent(fn, mode, handler, options) {
|
|
|
2965
3524
|
const agentHandler = { mode, handler, options: resolveRunOptions(options, mode, logger) };
|
|
2966
3525
|
const rt = createRuntime(options, agentHandler);
|
|
2967
3526
|
if (mode === "remote" && options.pollIntervalMs !== void 0) rt.pollIntervalMs = options.pollIntervalMs;
|
|
3527
|
+
if (mode === "remote") rt.runId = resolveRunId(options.runId);
|
|
2968
3528
|
runtime = rt;
|
|
2969
3529
|
scheduleBoot(rt);
|
|
2970
3530
|
}
|
|
@@ -3005,7 +3565,9 @@ function captureException(error, hint) {
|
|
|
3005
3565
|
const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
3006
3566
|
forwardAttachment(rt, hint.attachment, message, hint.type ?? "error");
|
|
3007
3567
|
}
|
|
3008
|
-
|
|
3568
|
+
const runId = currentReporter()?.runId;
|
|
3569
|
+
const withRun = runId ? { ...hint, tags: { run_id: runId, ...hint?.tags } } : hint;
|
|
3570
|
+
return rt.client.captureException(error, withRun);
|
|
3009
3571
|
}
|
|
3010
3572
|
async function close(timeoutMs = 3e4) {
|
|
3011
3573
|
const rt = runtime;
|
|
@@ -3014,6 +3576,7 @@ async function close(timeoutMs = 3e4) {
|
|
|
3014
3576
|
await rt.bootPromise;
|
|
3015
3577
|
if (rt.agent) await rt.agent.stop(0);
|
|
3016
3578
|
rt.local?.stop();
|
|
3579
|
+
rt.attached?.stop();
|
|
3017
3580
|
const idle = await rt.tracker.waitIdle(timeoutMs);
|
|
3018
3581
|
if (!idle) {
|
|
3019
3582
|
rt.logger.warn(`${rt.tracker.activeCount} run(s) ainda em execu\xE7\xE3o ap\xF3s ${timeoutMs}ms \u2014 abandonando a espera`);
|
|
@@ -3065,6 +3628,14 @@ var fallbackItems = {
|
|
|
3065
3628
|
each: (list, handler, options = {}) => eachItem(list, handler, options, fallbackEachSink)
|
|
3066
3629
|
};
|
|
3067
3630
|
var fallbackProgress = { total: noop, advance: noop, set: noop, current: 0, snapshot: null };
|
|
3631
|
+
var outsideRunFiles = () => Promise.reject(new Error("hobots.files s\xF3 baixa arquivos dentro de uma execu\xE7\xE3o (handler do register()/start())"));
|
|
3632
|
+
var fallbackFiles = {
|
|
3633
|
+
list: () => [],
|
|
3634
|
+
get: () => void 0,
|
|
3635
|
+
download: outsideRunFiles,
|
|
3636
|
+
downloadAll: outsideRunFiles,
|
|
3637
|
+
read: outsideRunFiles
|
|
3638
|
+
};
|
|
3068
3639
|
var neverAborts = new AbortController().signal;
|
|
3069
3640
|
var detachedTransaction;
|
|
3070
3641
|
function resolve(member) {
|
|
@@ -3086,6 +3657,7 @@ var hobots = Object.defineProperties({}, {
|
|
|
3086
3657
|
log: getter("log", () => fallbackLog),
|
|
3087
3658
|
items: getter("items", () => fallbackItems),
|
|
3088
3659
|
progress: getter("progress", () => fallbackProgress),
|
|
3660
|
+
files: getter("files", () => fallbackFiles),
|
|
3089
3661
|
transaction: getter("transaction", () => detachedTransaction ??= new Transaction(void 0, "hobots.detached")),
|
|
3090
3662
|
signal: getter("signal", () => neverAborts),
|
|
3091
3663
|
throwIfCanceled: method(() => resolve("throwIfCanceled")?.throwIfCanceled()),
|
|
@@ -3106,7 +3678,8 @@ var hobots = Object.defineProperties({}, {
|
|
|
3106
3678
|
}),
|
|
3107
3679
|
close: method((timeoutMs) => close(timeoutMs)),
|
|
3108
3680
|
captureException: method((error, hint) => captureException(error, hint)),
|
|
3109
|
-
image: method(image)
|
|
3681
|
+
image: method(image),
|
|
3682
|
+
isFileRef: method(isFileRef)
|
|
3110
3683
|
});
|
|
3111
3684
|
function installGlobalHobots() {
|
|
3112
3685
|
if (globalThis.hobots === hobots) return;
|
|
@@ -3118,7 +3691,9 @@ var close2 = hobots.close;
|
|
|
3118
3691
|
var captureException2 = hobots.captureException;
|
|
3119
3692
|
var retry = hobots.retry;
|
|
3120
3693
|
var image2 = hobots.image;
|
|
3694
|
+
var isFileRef2 = hobots.isFileRef;
|
|
3121
3695
|
|
|
3122
3696
|
// src/index.ts
|
|
3697
|
+
loadDotenv();
|
|
3123
3698
|
var src_default = hobots;
|
|
3124
3699
|
//# sourceMappingURL=index.cjs.map
|