hobots 3.0.0 → 3.2.1
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 +9 -3
- package/dist/index.cjs +177 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -14
- package/dist/index.d.ts +37 -14
- package/dist/index.js +177 -66
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +13 -1
- package/dist/protocol.d.ts +13 -1
- package/docs/api-reference.md +3 -2
- package/docs/protocol.md +4 -1
- package/docs/tasks.md +15 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/stacktrace.ts
|
|
2
2
|
var MAX_FRAMES = 50;
|
|
3
|
+
var MAX_FILENAME_CHARS = 1024;
|
|
3
4
|
var FRAME_RE = /^\s*at\s+(?:async\s+)?(?:(.+?)\s+\()?(.+?)\)?$/;
|
|
4
5
|
var LOCATION_RE = /^(.*?):(\d+):(\d+)$/;
|
|
5
6
|
function isInApp(filename) {
|
|
@@ -22,7 +23,7 @@ function parseStacktrace(stack) {
|
|
|
22
23
|
const filename = loc ? loc[1] : location;
|
|
23
24
|
frames.push({
|
|
24
25
|
function: fn || "<anonymous>",
|
|
25
|
-
filename: filename
|
|
26
|
+
filename: filename ? filename.slice(0, MAX_FILENAME_CHARS) : void 0,
|
|
26
27
|
lineno: loc?.[2] ? Number(loc[2]) : void 0,
|
|
27
28
|
colno: loc?.[3] ? Number(loc[3]) : void 0,
|
|
28
29
|
in_app: isInApp(filename)
|
|
@@ -52,7 +53,7 @@ function scrubSecrets(text) {
|
|
|
52
53
|
|
|
53
54
|
// src/utils.ts
|
|
54
55
|
var SDK_NAME = "hobots";
|
|
55
|
-
var SDK_VERSION = true ? "3.
|
|
56
|
+
var SDK_VERSION = true ? "3.2.0" : "0.0.0-dev";
|
|
56
57
|
function isNode() {
|
|
57
58
|
return typeof process !== "undefined" && typeof process.versions?.node === "string";
|
|
58
59
|
}
|
|
@@ -108,9 +109,19 @@ function jitter(baseMs) {
|
|
|
108
109
|
function truncate(value, max) {
|
|
109
110
|
return value.length > max ? `${value.slice(0, max)}\u2026 [truncado]` : value;
|
|
110
111
|
}
|
|
112
|
+
function safeString(value) {
|
|
113
|
+
if (typeof value === "string") return value;
|
|
114
|
+
try {
|
|
115
|
+
const json = JSON.stringify(value);
|
|
116
|
+
if (json && json !== "{}") return json;
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
return String(value);
|
|
120
|
+
}
|
|
111
121
|
var DEFAULT_RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
|
|
112
122
|
async function fetchWithRetry(send, options) {
|
|
113
123
|
const delays = options.delays ?? DEFAULT_RETRY_DELAYS_MS;
|
|
124
|
+
const noRetry = options.noRetryStatuses ?? [];
|
|
114
125
|
for (let attempt = 0; ; attempt++) {
|
|
115
126
|
let retriable;
|
|
116
127
|
let serverDelay;
|
|
@@ -118,6 +129,7 @@ async function fetchWithRetry(send, options) {
|
|
|
118
129
|
const response = await send();
|
|
119
130
|
if (response.ok) return response;
|
|
120
131
|
if (response.status >= 400 && response.status < 500 && response.status !== 429) return response;
|
|
132
|
+
if (noRetry.includes(response.status)) return response;
|
|
121
133
|
if (response.status === 429) serverDelay = retryAfterMs(response);
|
|
122
134
|
retriable = new Error(`HTTP ${response.status}`);
|
|
123
135
|
} catch (error) {
|
|
@@ -190,26 +202,18 @@ function colorize(text, color) {
|
|
|
190
202
|
}
|
|
191
203
|
|
|
192
204
|
// src/eventbuilder.ts
|
|
193
|
-
|
|
194
|
-
if (typeof value === "string") return value;
|
|
195
|
-
try {
|
|
196
|
-
const json = JSON.stringify(value);
|
|
197
|
-
if (json && json !== "{}") return json;
|
|
198
|
-
} catch {
|
|
199
|
-
}
|
|
200
|
-
return String(value);
|
|
201
|
-
}
|
|
205
|
+
var MAX_EXCEPTION_VALUE_CHARS = 8192;
|
|
202
206
|
function eventFromException(error, type = "error") {
|
|
203
207
|
let exceptionType;
|
|
204
208
|
let value;
|
|
205
209
|
let frames = [];
|
|
206
210
|
if (error instanceof Error) {
|
|
207
211
|
exceptionType = error.name || "Error";
|
|
208
|
-
value = scrubSecrets(error.message);
|
|
212
|
+
value = truncate(scrubSecrets(error.message), MAX_EXCEPTION_VALUE_CHARS);
|
|
209
213
|
frames = parseStacktrace(error.stack);
|
|
210
214
|
} else {
|
|
211
215
|
exceptionType = "NonError";
|
|
212
|
-
value = scrubSecrets(safeString(error));
|
|
216
|
+
value = truncate(scrubSecrets(safeString(error)), MAX_EXCEPTION_VALUE_CHARS);
|
|
213
217
|
}
|
|
214
218
|
return {
|
|
215
219
|
event_id: uuid4(),
|
|
@@ -335,6 +339,18 @@ var Transport = class {
|
|
|
335
339
|
};
|
|
336
340
|
|
|
337
341
|
// src/client.ts
|
|
342
|
+
var MAX_TAG_VALUE_CHARS = 512;
|
|
343
|
+
var MAX_FINGERPRINT_ITEMS = 10;
|
|
344
|
+
var MAX_FINGERPRINT_ITEM_CHARS = 255;
|
|
345
|
+
var MAX_TRANSACTION_NAME_CHARS = 500;
|
|
346
|
+
var MAX_STEPS = 200;
|
|
347
|
+
function clipTags(tags) {
|
|
348
|
+
return Object.fromEntries(Object.entries(tags).map(([key, value]) => [key, truncate(String(value), MAX_TAG_VALUE_CHARS)]));
|
|
349
|
+
}
|
|
350
|
+
function clipFingerprint(fingerprint) {
|
|
351
|
+
if (fingerprint === void 0) return void 0;
|
|
352
|
+
return fingerprint.slice(0, MAX_FINGERPRINT_ITEMS).map((item) => truncate(String(item), MAX_FINGERPRINT_ITEM_CHARS));
|
|
353
|
+
}
|
|
338
354
|
function runtimeContexts() {
|
|
339
355
|
if (isNode()) {
|
|
340
356
|
return {
|
|
@@ -403,20 +419,27 @@ var Client = class {
|
|
|
403
419
|
type: hint?.type ?? event.type ?? "error",
|
|
404
420
|
release: event.release ?? this.options.release,
|
|
405
421
|
environment: this.resolveEnvironment(event.environment),
|
|
406
|
-
tags: Object.keys(tags).length > 0 ? tags : void 0,
|
|
422
|
+
tags: Object.keys(tags).length > 0 ? clipTags(tags) : void 0,
|
|
407
423
|
extra: Object.keys(extra).length > 0 ? extra : void 0,
|
|
408
424
|
contexts: { ...runtimeContexts(), ...machine, ...event.contexts },
|
|
409
|
-
fingerprint: hint?.fingerprint ?? event.fingerprint
|
|
425
|
+
fingerprint: clipFingerprint(hint?.fingerprint ?? event.fingerprint)
|
|
410
426
|
};
|
|
411
427
|
}
|
|
412
428
|
sendTransaction(payload) {
|
|
429
|
+
let steps = payload.steps;
|
|
430
|
+
if (steps.length > MAX_STEPS) {
|
|
431
|
+
this.logger.warn(`transaction com ${steps.length} steps \u2014 enviando as primeiras ${MAX_STEPS}`);
|
|
432
|
+
steps = steps.slice(0, MAX_STEPS);
|
|
433
|
+
}
|
|
413
434
|
this.transport.enqueue({
|
|
414
435
|
type: "transaction",
|
|
415
436
|
payload: {
|
|
416
437
|
...payload,
|
|
438
|
+
name: truncate(payload.name || "(sem nome)", MAX_TRANSACTION_NAME_CHARS),
|
|
439
|
+
steps,
|
|
417
440
|
release: payload.release ?? this.options.release,
|
|
418
441
|
environment: this.resolveEnvironment(payload.environment),
|
|
419
|
-
tags: { ...this.options.tags, ...payload.tags }
|
|
442
|
+
tags: clipTags({ ...this.options.tags, ...payload.tags })
|
|
420
443
|
}
|
|
421
444
|
});
|
|
422
445
|
}
|
|
@@ -509,12 +532,19 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
509
532
|
|
|
510
533
|
// src/env.ts
|
|
511
534
|
var INSTANCE_ID_ENV = "HOBOTS_INSTANCE_ID";
|
|
535
|
+
var ENVIRONMENT_ENV = "HOBOTS_ENVIRONMENT";
|
|
512
536
|
function resolveInstanceId(option) {
|
|
513
537
|
const fromOption = option?.trim();
|
|
514
538
|
if (fromOption) return fromOption;
|
|
515
539
|
if (typeof process === "undefined" || !process.env) return void 0;
|
|
516
540
|
return process.env[INSTANCE_ID_ENV]?.trim() || void 0;
|
|
517
541
|
}
|
|
542
|
+
function resolveEnvironmentOption(option) {
|
|
543
|
+
const fromOption = option?.trim();
|
|
544
|
+
if (fromOption) return fromOption;
|
|
545
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
546
|
+
return process.env[ENVIRONMENT_ENV]?.trim() || void 0;
|
|
547
|
+
}
|
|
518
548
|
async function fallbackInstanceId() {
|
|
519
549
|
if (!isNode()) return "browser";
|
|
520
550
|
try {
|
|
@@ -593,6 +623,11 @@ function externalListenerCount(event) {
|
|
|
593
623
|
var emergencyTasks = [];
|
|
594
624
|
function addEmergencyTask(task) {
|
|
595
625
|
emergencyTasks.push(task);
|
|
626
|
+
const forget = () => {
|
|
627
|
+
const index = emergencyTasks.indexOf(task);
|
|
628
|
+
if (index !== -1) emergencyTasks.splice(index, 1);
|
|
629
|
+
};
|
|
630
|
+
task.then(forget, forget);
|
|
596
631
|
}
|
|
597
632
|
async function awaitEmergencyTasks(capMs) {
|
|
598
633
|
if (emergencyTasks.length === 0) return;
|
|
@@ -787,6 +822,8 @@ var MachineSampler = class {
|
|
|
787
822
|
// src/tasks/api.ts
|
|
788
823
|
var POLL_TIMEOUT_MS = 3e4;
|
|
789
824
|
var UPDATE_TIMEOUT_MS = 1e4;
|
|
825
|
+
var ATTACHMENT_TERMINAL_STATUSES = [429, 501, 503];
|
|
826
|
+
var ITEMS_TERMINAL_STATUSES = [429];
|
|
790
827
|
var UPLOAD_TIMEOUT_MS = 3e4;
|
|
791
828
|
var UPLOAD_RETRY_DELAYS_MS = [1e3, 4e3];
|
|
792
829
|
var TasksApiError = class extends Error {
|
|
@@ -796,6 +833,14 @@ var TasksApiError = class extends Error {
|
|
|
796
833
|
this.name = "TasksApiError";
|
|
797
834
|
}
|
|
798
835
|
};
|
|
836
|
+
async function withRequestTimeout(timeoutMs, external, send) {
|
|
837
|
+
const { signal, cleanup } = withTimeoutSignal(timeoutMs, external);
|
|
838
|
+
try {
|
|
839
|
+
return await send(signal);
|
|
840
|
+
} finally {
|
|
841
|
+
cleanup();
|
|
842
|
+
}
|
|
843
|
+
}
|
|
799
844
|
var TasksApi = class {
|
|
800
845
|
baseUrl;
|
|
801
846
|
key;
|
|
@@ -932,12 +977,9 @@ var TasksApi = class {
|
|
|
932
977
|
body: JSON.stringify(request),
|
|
933
978
|
signal: AbortSignal.timeout(UPDATE_TIMEOUT_MS)
|
|
934
979
|
}),
|
|
935
|
-
{ logger: this.logger, label: `itens da run ${runId}
|
|
980
|
+
{ logger: this.logger, label: `itens da run ${runId}`, noRetryStatuses: ITEMS_TERMINAL_STATUSES }
|
|
936
981
|
);
|
|
937
982
|
} catch (error) {
|
|
938
|
-
if (error instanceof Error && error.message === "HTTP 429") {
|
|
939
|
-
throw new TasksApiError("envio de itens rejeitado (HTTP 429)", 429);
|
|
940
|
-
}
|
|
941
983
|
throw new Error(`envio de itens da run ${runId} descartado ap\xF3s retries: ${String(error)}`);
|
|
942
984
|
}
|
|
943
985
|
if (!response.ok) {
|
|
@@ -954,21 +996,19 @@ var TasksApi = class {
|
|
|
954
996
|
* "esta instalação não tem anexos" — o RunReporter degrada em silêncio a partir daí.
|
|
955
997
|
*/
|
|
956
998
|
async createAttachment(runId, request, signal) {
|
|
957
|
-
const
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
() => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}/attachments`, {
|
|
999
|
+
const response = await fetchWithRetry(
|
|
1000
|
+
() => withRequestTimeout(
|
|
1001
|
+
UPDATE_TIMEOUT_MS,
|
|
1002
|
+
signal,
|
|
1003
|
+
(timeoutSignal) => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}/attachments`, {
|
|
962
1004
|
method: "POST",
|
|
963
1005
|
headers: this.headers(),
|
|
964
1006
|
body: JSON.stringify(request),
|
|
965
1007
|
signal: timeoutSignal
|
|
966
|
-
})
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
cleanup();
|
|
971
|
-
}
|
|
1008
|
+
})
|
|
1009
|
+
),
|
|
1010
|
+
{ logger: this.logger, label: `anexo da run ${runId}`, noRetryStatuses: ATTACHMENT_TERMINAL_STATUSES }
|
|
1011
|
+
);
|
|
972
1012
|
if (!response.ok) {
|
|
973
1013
|
throw new TasksApiError(`cria\xE7\xE3o de anexo rejeitada (HTTP ${response.status})`, response.status);
|
|
974
1014
|
}
|
|
@@ -988,22 +1028,20 @@ var TasksApi = class {
|
|
|
988
1028
|
* está na assinatura da URL, e mandar o client secret para fora da API seria vazamento.
|
|
989
1029
|
*/
|
|
990
1030
|
async uploadAttachment(url, headers, body, signal) {
|
|
991
|
-
const
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
() => fetch(url, {
|
|
1031
|
+
const response = await fetchWithRetry(
|
|
1032
|
+
() => withRequestTimeout(
|
|
1033
|
+
UPLOAD_TIMEOUT_MS,
|
|
1034
|
+
signal,
|
|
1035
|
+
(timeoutSignal) => fetch(url, {
|
|
996
1036
|
method: "PUT",
|
|
997
1037
|
headers,
|
|
998
1038
|
// BodyInit não aceita Uint8Array<ArrayBufferLike> em todos os lib.dom; o buffer é o mesmo
|
|
999
1039
|
body,
|
|
1000
1040
|
signal: timeoutSignal
|
|
1001
|
-
})
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
cleanup();
|
|
1006
|
-
}
|
|
1041
|
+
})
|
|
1042
|
+
),
|
|
1043
|
+
{ delays: UPLOAD_RETRY_DELAYS_MS, logger: this.logger, label: "upload de anexo" }
|
|
1044
|
+
);
|
|
1007
1045
|
if (!response.ok) {
|
|
1008
1046
|
throw new TasksApiError(`upload de anexo rejeitado (HTTP ${response.status})`, response.status);
|
|
1009
1047
|
}
|
|
@@ -1041,10 +1079,12 @@ var Backoff = class {
|
|
|
1041
1079
|
this.authFailures = 0;
|
|
1042
1080
|
}
|
|
1043
1081
|
next(error) {
|
|
1044
|
-
if (error instanceof TasksApiError && error.status === 401) {
|
|
1082
|
+
if (error instanceof TasksApiError && (error.status === 401 || error.status === 402)) {
|
|
1045
1083
|
this.authFailures++;
|
|
1046
1084
|
if (this.authFailures === 1 || this.authFailures % 10 === 0) {
|
|
1047
|
-
this.logger.error(
|
|
1085
|
+
this.logger.error(
|
|
1086
|
+
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 do agente em Configura\xE7\xF5es \u2192 Agentes"
|
|
1087
|
+
);
|
|
1048
1088
|
}
|
|
1049
1089
|
return Math.max(this.floorMs, AUTH_FAILURE_INTERVAL_MS);
|
|
1050
1090
|
}
|
|
@@ -1078,7 +1118,8 @@ function resolveDelay(delay, attempt, fallbackMs) {
|
|
|
1078
1118
|
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
1079
1119
|
}
|
|
1080
1120
|
async function retryWithBackoff(fn, options = {}) {
|
|
1081
|
-
const
|
|
1121
|
+
const requested = options.retries ?? DEFAULT_RETRIES;
|
|
1122
|
+
const retries = Number.isFinite(requested) ? Math.max(0, Math.floor(requested)) : DEFAULT_RETRIES;
|
|
1082
1123
|
const wait = options.wait ?? sleep;
|
|
1083
1124
|
for (let attempt = 1; ; attempt++) {
|
|
1084
1125
|
if (options.signal?.aborted) throw runCanceledError();
|
|
@@ -1113,6 +1154,7 @@ function startsWith(bytes, signature, offset = 0) {
|
|
|
1113
1154
|
return signature.every((byte, index) => bytes[offset + index] === byte);
|
|
1114
1155
|
}
|
|
1115
1156
|
var PNG_MAGIC = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
1157
|
+
var MAX_PNG_DIMENSION = 2e4;
|
|
1116
1158
|
var JPEG_MAGIC = [255, 216, 255];
|
|
1117
1159
|
var RIFF_MAGIC = [82, 73, 70, 70];
|
|
1118
1160
|
var WEBP_MAGIC = [87, 69, 66, 80];
|
|
@@ -1128,6 +1170,7 @@ function readPngDimensions(bytes) {
|
|
|
1128
1170
|
const width = view.getUint32(16);
|
|
1129
1171
|
const height = view.getUint32(20);
|
|
1130
1172
|
if (width === 0 || height === 0) return void 0;
|
|
1173
|
+
if (width > MAX_PNG_DIMENSION || height > MAX_PNG_DIMENSION) return void 0;
|
|
1131
1174
|
return { width, height };
|
|
1132
1175
|
}
|
|
1133
1176
|
async function toBytes(input) {
|
|
@@ -1144,6 +1187,10 @@ var LOG_LEVEL_COLOR = {
|
|
|
1144
1187
|
error: "red",
|
|
1145
1188
|
success: "green"
|
|
1146
1189
|
};
|
|
1190
|
+
function joinLogArgs(message, rest) {
|
|
1191
|
+
if (rest.length === 0) return safeString(message);
|
|
1192
|
+
return [message, ...rest].map(safeString).join(" ");
|
|
1193
|
+
}
|
|
1147
1194
|
function printRunLog(task, level, message) {
|
|
1148
1195
|
const line = colorize(`[hobots:${task}] ${level.toUpperCase()}`, LOG_LEVEL_COLOR[level]);
|
|
1149
1196
|
const method2 = level === "warning" ? "warn" : level === "success" ? "info" : level;
|
|
@@ -1201,11 +1248,11 @@ var RunReporter = class {
|
|
|
1201
1248
|
Math.max(MIN_CANCEL_CHECK_INTERVAL_MS, cancelCheckIntervalMs ?? DEFAULT_CANCEL_CHECK_INTERVAL_MS)
|
|
1202
1249
|
);
|
|
1203
1250
|
const log = {
|
|
1204
|
-
debug: (message) => this.log("debug", message),
|
|
1205
|
-
info: (message) => this.log("info", message),
|
|
1206
|
-
warn: (message) => this.log("warning", message),
|
|
1207
|
-
error: (message) => this.log("error", message),
|
|
1208
|
-
success: (message) => this.log("success", message)
|
|
1251
|
+
debug: (message, ...rest) => this.log("debug", joinLogArgs(message, rest)),
|
|
1252
|
+
info: (message, ...rest) => this.log("info", joinLogArgs(message, rest)),
|
|
1253
|
+
warn: (message, ...rest) => this.log("warning", joinLogArgs(message, rest)),
|
|
1254
|
+
error: (message, ...rest) => this.log("error", joinLogArgs(message, rest)),
|
|
1255
|
+
success: (message, ...rest) => this.log("success", joinLogArgs(message, rest))
|
|
1209
1256
|
};
|
|
1210
1257
|
tx.setLifecycleSink((level, message) => this.push(level, message));
|
|
1211
1258
|
const checkCanceled = () => this.checkCanceled();
|
|
@@ -1355,7 +1402,7 @@ var RunReporter = class {
|
|
|
1355
1402
|
}
|
|
1356
1403
|
log(level, message) {
|
|
1357
1404
|
if (this.dead) return;
|
|
1358
|
-
const scrubbed = scrubSecrets(message);
|
|
1405
|
+
const scrubbed = scrubSecrets(safeString(message));
|
|
1359
1406
|
printRunLog(this.context.task, level, scrubbed);
|
|
1360
1407
|
this.push(level, scrubbed, true);
|
|
1361
1408
|
}
|
|
@@ -1414,13 +1461,25 @@ var RunReporter = class {
|
|
|
1414
1461
|
this.stopCancelWatch();
|
|
1415
1462
|
this.clearFlushTimer();
|
|
1416
1463
|
this.forceFinalProgress();
|
|
1417
|
-
this.enqueue({ status: "succeeded", logs: this.
|
|
1464
|
+
this.enqueue({ status: "succeeded", logs: this.takeLogsForTerminal() });
|
|
1418
1465
|
}
|
|
1419
1466
|
reportFailure(error) {
|
|
1420
1467
|
this.stopCancelWatch();
|
|
1421
1468
|
this.clearFlushTimer();
|
|
1422
1469
|
this.forceFinalProgress();
|
|
1423
|
-
this.enqueue({ status: "failed", error, logs: this.
|
|
1470
|
+
this.enqueue({ status: "failed", error, logs: this.takeLogsForTerminal() });
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Esvazia o buffer para o PATCH terminal respeitando o teto de 100 logs por PATCH:
|
|
1474
|
+
* o excedente sai antes, em PATCHes `running` na mesma cadeia (a ordem se mantém).
|
|
1475
|
+
*/
|
|
1476
|
+
takeLogsForTerminal() {
|
|
1477
|
+
let logs = this.takeLogs();
|
|
1478
|
+
while (logs.length > MAX_LOGS_PER_PATCH) {
|
|
1479
|
+
this.enqueue({ status: "running", logs: logs.slice(0, MAX_LOGS_PER_PATCH) });
|
|
1480
|
+
logs = logs.slice(MAX_LOGS_PER_PATCH);
|
|
1481
|
+
}
|
|
1482
|
+
return logs;
|
|
1424
1483
|
}
|
|
1425
1484
|
// ── Progresso ────────────────────────────────────────────────────────────────
|
|
1426
1485
|
/** Valida, clampa e marca o snapshot como pendente; agenda flush só se nada mais vai sair. */
|
|
@@ -1528,7 +1587,7 @@ var RunReporter = class {
|
|
|
1528
1587
|
id: id ? truncate(String(id), 255) : uuid4(),
|
|
1529
1588
|
seq: this.itemSeq++,
|
|
1530
1589
|
status,
|
|
1531
|
-
...message ? { message: truncate(scrubSecrets(message), MAX_ITEM_MESSAGE_CHARS) } : {},
|
|
1590
|
+
...message ? { message: truncate(scrubSecrets(safeString(message)), MAX_ITEM_MESSAGE_CHARS) } : {},
|
|
1532
1591
|
...scrubbedPayload ? { payload: scrubbedPayload } : {},
|
|
1533
1592
|
ts: nowISO()
|
|
1534
1593
|
});
|
|
@@ -1617,8 +1676,10 @@ var RunReporter = class {
|
|
|
1617
1676
|
async attach(image, options = {}) {
|
|
1618
1677
|
const id = uuid4();
|
|
1619
1678
|
const level = options.level ?? "info";
|
|
1620
|
-
const
|
|
1621
|
-
const
|
|
1679
|
+
const rawCaption = options.caption !== void 0 ? safeString(options.caption) : void 0;
|
|
1680
|
+
const rawMessage = options.message !== void 0 ? safeString(options.message) : void 0;
|
|
1681
|
+
const caption = rawCaption ? truncate(scrubSecrets(rawCaption), MAX_CAPTION_CHARS) : void 0;
|
|
1682
|
+
const message = rawMessage ?? rawCaption ?? "captura de tela";
|
|
1622
1683
|
if (this.dead) return { id, ok: false, reason: "canceled" };
|
|
1623
1684
|
if (this.attachmentsUnsupported) {
|
|
1624
1685
|
this.log(level, message);
|
|
@@ -2238,6 +2299,42 @@ var TaskAgent = class {
|
|
|
2238
2299
|
}
|
|
2239
2300
|
};
|
|
2240
2301
|
|
|
2302
|
+
// src/tasks/item-columns.ts
|
|
2303
|
+
var KEY_RE = /^[a-zA-Z0-9_.-]{1,100}$/;
|
|
2304
|
+
var MAX_COLUMNS = 12;
|
|
2305
|
+
var MAX_LABEL_CHARS = 60;
|
|
2306
|
+
function normalizeItemColumns(slug, input, logger) {
|
|
2307
|
+
if (input === void 0 || input === null) return void 0;
|
|
2308
|
+
const columns = input.columns;
|
|
2309
|
+
if (typeof input !== "object" || !Array.isArray(columns)) {
|
|
2310
|
+
logger.warn(`task "${slug}": items.columns precisa ser uma lista \u2014 declara\xE7\xE3o de colunas ignorada`);
|
|
2311
|
+
return void 0;
|
|
2312
|
+
}
|
|
2313
|
+
const result = [];
|
|
2314
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2315
|
+
for (const entry of columns) {
|
|
2316
|
+
const rawKey = typeof entry === "string" ? entry : entry?.key;
|
|
2317
|
+
const rawLabel = typeof entry === "object" && entry !== null ? entry.label : void 0;
|
|
2318
|
+
const key = typeof rawKey === "string" ? rawKey.trim() : "";
|
|
2319
|
+
if (!KEY_RE.test(key)) {
|
|
2320
|
+
logger.warn(`task "${slug}": coluna de item inv\xE1lida ignorada (${JSON.stringify(rawKey)}) \u2014 use letras, n\xFAmeros, ponto, h\xEDfen e underline`);
|
|
2321
|
+
continue;
|
|
2322
|
+
}
|
|
2323
|
+
if (seen.has(key)) {
|
|
2324
|
+
logger.warn(`task "${slug}": coluna de item "${key}" repetida ignorada`);
|
|
2325
|
+
continue;
|
|
2326
|
+
}
|
|
2327
|
+
if (result.length >= MAX_COLUMNS) {
|
|
2328
|
+
logger.warn(`task "${slug}": no m\xE1ximo ${MAX_COLUMNS} colunas de item \u2014 "${key}" em diante ignoradas`);
|
|
2329
|
+
break;
|
|
2330
|
+
}
|
|
2331
|
+
seen.add(key);
|
|
2332
|
+
const label = typeof rawLabel === "string" ? rawLabel.trim().slice(0, MAX_LABEL_CHARS) : "";
|
|
2333
|
+
result.push(label ? { key, label } : { key });
|
|
2334
|
+
}
|
|
2335
|
+
return result.length > 0 ? result : void 0;
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2241
2338
|
// src/tasks/local.ts
|
|
2242
2339
|
var LocalRunner = class {
|
|
2243
2340
|
constructor(task, deps) {
|
|
@@ -2316,6 +2413,15 @@ var TaskRegistry = class {
|
|
|
2316
2413
|
allSlugs() {
|
|
2317
2414
|
return [...this.tasks.keys()];
|
|
2318
2415
|
}
|
|
2416
|
+
/** Colunas de itens declaradas no código, por slug (`instance.task_items`); undefined se nenhuma task declarou. */
|
|
2417
|
+
itemDeclarations() {
|
|
2418
|
+
let result;
|
|
2419
|
+
for (const task of this.tasks.values()) {
|
|
2420
|
+
if (!task.options.itemColumns) continue;
|
|
2421
|
+
(result ??= {})[task.slug] = { attributes: task.options.itemColumns };
|
|
2422
|
+
}
|
|
2423
|
+
return result;
|
|
2424
|
+
}
|
|
2319
2425
|
clear() {
|
|
2320
2426
|
this.tasks.clear();
|
|
2321
2427
|
}
|
|
@@ -2469,8 +2575,9 @@ function ensureRuntime(options) {
|
|
|
2469
2575
|
}
|
|
2470
2576
|
const config = resolveConfig({ clientSecret: options.clientSecret });
|
|
2471
2577
|
const logger = makeLogger(options.debug ?? false);
|
|
2578
|
+
const environment = resolveEnvironmentOption(options.environment);
|
|
2472
2579
|
const client = new Client(
|
|
2473
|
-
{ environment
|
|
2580
|
+
{ environment, release: options.release, tags: options.tags, debug: options.debug },
|
|
2474
2581
|
config
|
|
2475
2582
|
);
|
|
2476
2583
|
const api = new TasksApi({ baseUrl: config.tasksBaseUrl, key: config.key, logger });
|
|
@@ -2487,6 +2594,7 @@ function ensureRuntime(options) {
|
|
|
2487
2594
|
agent: void 0,
|
|
2488
2595
|
locals: /* @__PURE__ */ new Map(),
|
|
2489
2596
|
instanceId: resolveInstanceId(options.instanceId),
|
|
2597
|
+
environment,
|
|
2490
2598
|
pollIntervalMs: void 0,
|
|
2491
2599
|
booted: false,
|
|
2492
2600
|
bootScheduled: false,
|
|
@@ -2511,7 +2619,7 @@ function clampConcurrency(value) {
|
|
|
2511
2619
|
if (value === void 0 || !Number.isFinite(value)) return 1;
|
|
2512
2620
|
return Math.min(MAX_CONCURRENCY, Math.max(1, Math.floor(value)));
|
|
2513
2621
|
}
|
|
2514
|
-
function resolveTaskOptions(options, mode) {
|
|
2622
|
+
function resolveTaskOptions(slug, options, mode, logger) {
|
|
2515
2623
|
const retries = options.retries === void 0 || !Number.isFinite(options.retries) ? DEFAULT_RETRIES2 : Math.max(0, Math.floor(options.retries));
|
|
2516
2624
|
return {
|
|
2517
2625
|
retries,
|
|
@@ -2520,7 +2628,8 @@ function resolveTaskOptions(options, mode) {
|
|
|
2520
2628
|
concurrency: mode === "remote" ? clampConcurrency(options.concurrency) : 1,
|
|
2521
2629
|
logFlushIntervalMs: options.logFlushIntervalMs,
|
|
2522
2630
|
cancelCheckIntervalMs: options.cancelCheckIntervalMs,
|
|
2523
|
-
cancelGraceMs: options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS
|
|
2631
|
+
cancelGraceMs: options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS,
|
|
2632
|
+
itemColumns: normalizeItemColumns(slug, options.items, logger)
|
|
2524
2633
|
};
|
|
2525
2634
|
}
|
|
2526
2635
|
function instanceInfo(rt) {
|
|
@@ -2529,9 +2638,11 @@ function instanceInfo(rt) {
|
|
|
2529
2638
|
version: SDK_VERSION,
|
|
2530
2639
|
host: hostname(),
|
|
2531
2640
|
// ambiente da instância: junto do heartbeat, é como o servidor sabe onde o robô roda
|
|
2532
|
-
environment: rt.
|
|
2641
|
+
environment: rt.environment,
|
|
2533
2642
|
// TODAS as tasks (register + start) — o painel mostra o que o agente sabe fazer
|
|
2534
2643
|
tasks: rt.registry.allSlugs(),
|
|
2644
|
+
// colunas da aba "itens" declaradas no código — a plataforma usa enquanto o usuário não personalizar
|
|
2645
|
+
task_items: rt.registry.itemDeclarations(),
|
|
2535
2646
|
// o servidor calcula a tolerância de offline (3×) a partir disto em agentes que fazem
|
|
2536
2647
|
// poll; clampado à faixa aceita pelo schema (1s..15min)
|
|
2537
2648
|
poll_interval_ms: rt.agent ? Math.min(Math.max(rt.agent.pollIntervalMs, 1e3), 9e5) : void 0,
|
|
@@ -2600,7 +2711,7 @@ function addTask(fn, mode, task, handler, options) {
|
|
|
2600
2711
|
rt.logger.warn('op\xE7\xE3o "pollIntervalMs" divergente ignorada \u2014 o primeiro register() define o intervalo do poll');
|
|
2601
2712
|
}
|
|
2602
2713
|
}
|
|
2603
|
-
const registered = { slug, mode, handler, options: resolveTaskOptions(options, mode) };
|
|
2714
|
+
const registered = { slug, mode, handler, options: resolveTaskOptions(slug, options, mode, rt.logger) };
|
|
2604
2715
|
rt.registry.add(registered);
|
|
2605
2716
|
if (!rt.booted) {
|
|
2606
2717
|
scheduleBoot(rt);
|
|
@@ -2689,11 +2800,11 @@ function warnOutsideRun(member) {
|
|
|
2689
2800
|
}
|
|
2690
2801
|
var FALLBACK_TASK = "hobots";
|
|
2691
2802
|
var fallbackLog = {
|
|
2692
|
-
debug: (message) => printRunLog(FALLBACK_TASK, "debug", message),
|
|
2693
|
-
info: (message) => printRunLog(FALLBACK_TASK, "info", message),
|
|
2694
|
-
warn: (message) => printRunLog(FALLBACK_TASK, "warning", message),
|
|
2695
|
-
error: (message) => printRunLog(FALLBACK_TASK, "error", message),
|
|
2696
|
-
success: (message) => printRunLog(FALLBACK_TASK, "success", message)
|
|
2803
|
+
debug: (message, ...rest) => printRunLog(FALLBACK_TASK, "debug", joinLogArgs(message, rest)),
|
|
2804
|
+
info: (message, ...rest) => printRunLog(FALLBACK_TASK, "info", joinLogArgs(message, rest)),
|
|
2805
|
+
warn: (message, ...rest) => printRunLog(FALLBACK_TASK, "warning", joinLogArgs(message, rest)),
|
|
2806
|
+
error: (message, ...rest) => printRunLog(FALLBACK_TASK, "error", joinLogArgs(message, rest)),
|
|
2807
|
+
success: (message, ...rest) => printRunLog(FALLBACK_TASK, "success", joinLogArgs(message, rest))
|
|
2697
2808
|
};
|
|
2698
2809
|
var noop = () => void 0;
|
|
2699
2810
|
var fallbackItems = { add: noop, succeeded: noop, failed: noop, occurrence: noop };
|