hobots 3.4.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -27
- package/dist/index.cjs +96 -208
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -25
- package/dist/index.d.ts +27 -25
- package/dist/index.js +96 -208
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +21 -21
- package/dist/protocol.d.ts +21 -21
- package/docs/README.md +10 -8
- package/docs/api-reference.md +33 -20
- package/docs/concepts.md +19 -19
- package/docs/getting-started.md +14 -12
- package/docs/protocol.md +22 -23
- package/docs/{tasks.md → runs.md} +21 -21
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -57,7 +57,7 @@ function scrubSecrets(text) {
|
|
|
57
57
|
|
|
58
58
|
// src/utils.ts
|
|
59
59
|
var SDK_NAME = "hobots";
|
|
60
|
-
var SDK_VERSION = true ? "
|
|
60
|
+
var SDK_VERSION = true ? "4.0.0" : "0.0.0-dev";
|
|
61
61
|
function isNode() {
|
|
62
62
|
return typeof process !== "undefined" && typeof process.versions?.node === "string";
|
|
63
63
|
}
|
|
@@ -514,7 +514,7 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
514
514
|
const origin = fromOption ? "clientSecret" : `env ${CLIENT_SECRET_ENV}`;
|
|
515
515
|
if (!key) {
|
|
516
516
|
throw new Error(
|
|
517
|
-
`${prefix} Client Secret ausente \u2014 defina a env ${CLIENT_SECRET_ENV} (ou passe clientSecret nas op\xE7\xF5es
|
|
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 do agente em Configura\xE7\xF5es \u2192 Agentes`
|
|
518
518
|
);
|
|
519
519
|
}
|
|
520
520
|
if (!SECRET_PATTERN.test(key)) {
|
|
@@ -527,7 +527,7 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
527
527
|
key,
|
|
528
528
|
baseUrl,
|
|
529
529
|
ingestEndpoint: `${baseUrl}/v1/ingest`,
|
|
530
|
-
|
|
530
|
+
runsBaseUrl: `${baseUrl}/v1/runs`
|
|
531
531
|
};
|
|
532
532
|
}
|
|
533
533
|
|
|
@@ -891,7 +891,7 @@ var TasksApi = class {
|
|
|
891
891
|
const { signal: timeoutSignal, cleanup } = withTimeoutSignal(UPDATE_TIMEOUT_MS, signal);
|
|
892
892
|
let response;
|
|
893
893
|
try {
|
|
894
|
-
response = await fetch(`${this.baseUrl}
|
|
894
|
+
response = await fetch(`${this.baseUrl}`, {
|
|
895
895
|
redirect: "error",
|
|
896
896
|
method: "POST",
|
|
897
897
|
headers: this.headers(),
|
|
@@ -905,12 +905,11 @@ var TasksApi = class {
|
|
|
905
905
|
throw new TasksApiError(`cria\xE7\xE3o da run falhou (HTTP ${response.status})`, response.status);
|
|
906
906
|
}
|
|
907
907
|
const body = await response.json().catch(() => null);
|
|
908
|
-
if (typeof body?.id !== "string"
|
|
909
|
-
throw new TasksApiError("resposta inv\xE1lida da cria\xE7\xE3o de run (id
|
|
908
|
+
if (typeof body?.id !== "string") {
|
|
909
|
+
throw new TasksApiError("resposta inv\xE1lida da cria\xE7\xE3o de run (id ausente)", 0);
|
|
910
910
|
}
|
|
911
911
|
return {
|
|
912
912
|
id: body.id,
|
|
913
|
-
task: body.task,
|
|
914
913
|
params: body.params && typeof body.params === "object" && !Array.isArray(body.params) ? body.params : {},
|
|
915
914
|
form: null,
|
|
916
915
|
source: typeof body.source === "string" ? body.source : "agent",
|
|
@@ -925,9 +924,9 @@ var TasksApi = class {
|
|
|
925
924
|
}
|
|
926
925
|
const valid = [];
|
|
927
926
|
for (const run of runs.slice(0, Math.max(0, max))) {
|
|
928
|
-
if (typeof run?.id !== "string"
|
|
927
|
+
if (typeof run?.id !== "string") continue;
|
|
929
928
|
const params = run.params && typeof run.params === "object" && !Array.isArray(run.params) ? run.params : {};
|
|
930
|
-
valid.push({ ...run, id: run.id,
|
|
929
|
+
valid.push({ ...run, id: run.id, params });
|
|
931
930
|
}
|
|
932
931
|
if (valid.length < runs.length) {
|
|
933
932
|
this.logger.warn(`poll retornou ${runs.length - valid.length} run(s) inv\xE1lida(s)/excedente(s) \u2014 descartadas`);
|
|
@@ -942,7 +941,7 @@ var TasksApi = class {
|
|
|
942
941
|
let response;
|
|
943
942
|
try {
|
|
944
943
|
response = await fetchWithRetry(
|
|
945
|
-
() => fetch(`${this.baseUrl}
|
|
944
|
+
() => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}`, {
|
|
946
945
|
redirect: "error",
|
|
947
946
|
method: "PATCH",
|
|
948
947
|
headers: this.headers(),
|
|
@@ -964,7 +963,7 @@ var TasksApi = class {
|
|
|
964
963
|
* que a run já foi cancelada/finalizada, o que é aceitável nesse caminho.
|
|
965
964
|
*/
|
|
966
965
|
async updateRunOnce(runId, update, timeoutMs = 2e3) {
|
|
967
|
-
await fetch(`${this.baseUrl}
|
|
966
|
+
await fetch(`${this.baseUrl}/${encodeURIComponent(runId)}`, {
|
|
968
967
|
redirect: "error",
|
|
969
968
|
method: "PATCH",
|
|
970
969
|
headers: this.headers(),
|
|
@@ -981,7 +980,7 @@ var TasksApi = class {
|
|
|
981
980
|
let response;
|
|
982
981
|
try {
|
|
983
982
|
response = await fetchWithRetry(
|
|
984
|
-
() => fetch(`${this.baseUrl}
|
|
983
|
+
() => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/items`, {
|
|
985
984
|
redirect: "error",
|
|
986
985
|
method: "POST",
|
|
987
986
|
headers: this.headers(),
|
|
@@ -1011,7 +1010,7 @@ var TasksApi = class {
|
|
|
1011
1010
|
() => withRequestTimeout(
|
|
1012
1011
|
UPDATE_TIMEOUT_MS,
|
|
1013
1012
|
signal,
|
|
1014
|
-
(timeoutSignal) => fetch(`${this.baseUrl}
|
|
1013
|
+
(timeoutSignal) => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/attachments`, {
|
|
1015
1014
|
redirect: "error",
|
|
1016
1015
|
method: "POST",
|
|
1017
1016
|
headers: this.headers(),
|
|
@@ -1062,7 +1061,7 @@ var TasksApi = class {
|
|
|
1062
1061
|
/** Marca o anexo como enviado (ou falho). Best-effort — a leitura não depende disso. */
|
|
1063
1062
|
async confirmAttachment(runId, attachmentId, request) {
|
|
1064
1063
|
const response = await fetch(
|
|
1065
|
-
`${this.baseUrl}
|
|
1064
|
+
`${this.baseUrl}/${encodeURIComponent(runId)}/attachments/${encodeURIComponent(attachmentId)}`,
|
|
1066
1065
|
{
|
|
1067
1066
|
redirect: "error",
|
|
1068
1067
|
method: "PATCH",
|
|
@@ -1320,8 +1319,8 @@ function splitLogArgs(message, rest) {
|
|
|
1320
1319
|
if (text.length === 0) return { text: last.caption !== void 0 ? safeString(last.caption) : "captura de tela", image: last };
|
|
1321
1320
|
return { text: joinLogArgs(text[0], text.slice(1)), image: last };
|
|
1322
1321
|
}
|
|
1323
|
-
function printRunLog(
|
|
1324
|
-
const line = colorize(`[hobots
|
|
1322
|
+
function printRunLog(level, message) {
|
|
1323
|
+
const line = colorize(`[hobots] ${level.toUpperCase()}`, LOG_LEVEL_COLOR[level]);
|
|
1325
1324
|
const method2 = level === "warning" ? "warn" : level === "success" ? "info" : level;
|
|
1326
1325
|
console[method2](line, message);
|
|
1327
1326
|
}
|
|
@@ -1367,7 +1366,7 @@ function scrubPayloadValue(value, depth) {
|
|
|
1367
1366
|
return out;
|
|
1368
1367
|
}
|
|
1369
1368
|
var RunReporter = class {
|
|
1370
|
-
constructor(api, runId,
|
|
1369
|
+
constructor(api, runId, logger, tx, flushIntervalMs, cancelCheckIntervalMs, params = {}) {
|
|
1371
1370
|
this.api = api;
|
|
1372
1371
|
this.runId = runId;
|
|
1373
1372
|
this.logger = logger;
|
|
@@ -1449,7 +1448,6 @@ var RunReporter = class {
|
|
|
1449
1448
|
const sleep2 = (ms) => cancelableSleep(ms, signal);
|
|
1450
1449
|
this.context = {
|
|
1451
1450
|
runId,
|
|
1452
|
-
task,
|
|
1453
1451
|
params,
|
|
1454
1452
|
log,
|
|
1455
1453
|
items,
|
|
@@ -1558,7 +1556,7 @@ var RunReporter = class {
|
|
|
1558
1556
|
const scrubbed = scrubSecrets(safeString(message));
|
|
1559
1557
|
const ts = nowISO();
|
|
1560
1558
|
const attachmentId = image3 === void 0 ? void 0 : this.queueAttachment(image3, ts);
|
|
1561
|
-
printRunLog(
|
|
1559
|
+
printRunLog(level, attachmentId ? `${scrubbed} [imagem]` : scrubbed);
|
|
1562
1560
|
this.push(level, scrubbed, true, attachmentId ? [attachmentId] : void 0, ts);
|
|
1563
1561
|
}
|
|
1564
1562
|
/** Bufferiza uma entrada de log sem eco no terminal (cap, truncate, flush). */
|
|
@@ -2203,7 +2201,7 @@ var Transaction = class {
|
|
|
2203
2201
|
if (this.attachmentSink) return this.attachmentSink(image3, nowISO());
|
|
2204
2202
|
if (!this.warnedNoAttachmentSink) {
|
|
2205
2203
|
this.warnedNoAttachmentSink = true;
|
|
2206
|
-
console.warn("[hobots] imagem de step descartada \u2014 s\xF3 \xE9 suportada dentro de uma execu\xE7\xE3o
|
|
2204
|
+
console.warn("[hobots] imagem de step descartada \u2014 s\xF3 \xE9 suportada dentro de uma execu\xE7\xE3o");
|
|
2207
2205
|
}
|
|
2208
2206
|
return void 0;
|
|
2209
2207
|
}
|
|
@@ -2269,13 +2267,12 @@ var Transaction = class {
|
|
|
2269
2267
|
// src/tasks/runner.ts
|
|
2270
2268
|
var DEFAULT_RETRY_DELAY_MS = 5e3;
|
|
2271
2269
|
async function executeRun(input) {
|
|
2272
|
-
const { run,
|
|
2273
|
-
const options =
|
|
2274
|
-
const tx = new Transaction(client,
|
|
2270
|
+
const { run, handler, api, client, logger, tracker } = input;
|
|
2271
|
+
const options = handler.options;
|
|
2272
|
+
const tx = new Transaction(client, "run", "hobots.run", run.id);
|
|
2275
2273
|
const reporter = new RunReporter(
|
|
2276
2274
|
api,
|
|
2277
2275
|
run.id,
|
|
2278
|
-
run.task,
|
|
2279
2276
|
logger,
|
|
2280
2277
|
tx,
|
|
2281
2278
|
options.logFlushIntervalMs,
|
|
@@ -2291,9 +2288,9 @@ async function executeRun(input) {
|
|
|
2291
2288
|
tx.finish();
|
|
2292
2289
|
return;
|
|
2293
2290
|
}
|
|
2294
|
-
logger.debug(`executando run ${run.id}
|
|
2291
|
+
logger.debug(`executando run ${run.id}`);
|
|
2295
2292
|
try {
|
|
2296
|
-
await runWithRunContext({ reporter }, () => invokeWithRetry(
|
|
2293
|
+
await runWithRunContext({ reporter }, () => invokeWithRetry(handler, run, reporter, logger));
|
|
2297
2294
|
await reporter.finalizeAttachments();
|
|
2298
2295
|
await reporter.finalizeItems();
|
|
2299
2296
|
warnIncompleteProgress(reporter);
|
|
@@ -2321,11 +2318,11 @@ function warnIncompleteProgress(reporter) {
|
|
|
2321
2318
|
`execu\xE7\xE3o conclu\xEDda com progresso incompleto: ${snapshot.current}/${snapshot.total} \u2014 o handler terminou sem processar (ou sem avan\xE7ar) todos os itens previstos`
|
|
2322
2319
|
);
|
|
2323
2320
|
}
|
|
2324
|
-
function invokeWithRetry(
|
|
2325
|
-
const { retries, retryDelayMs } =
|
|
2321
|
+
function invokeWithRetry(agent, run, reporter, logger) {
|
|
2322
|
+
const { retries, retryDelayMs } = agent.options;
|
|
2326
2323
|
const total = retries + 1;
|
|
2327
2324
|
const ctx = reporter.context;
|
|
2328
|
-
return retryWithBackoff(() => invokeWithTimeout(
|
|
2325
|
+
return retryWithBackoff(() => invokeWithTimeout(agent, run, reporter, logger), {
|
|
2329
2326
|
retries,
|
|
2330
2327
|
delayMs: (attempt) => resolveDelay(retryDelayMs, attempt, DEFAULT_RETRY_DELAY_MS),
|
|
2331
2328
|
shouldRetry: () => !reporter.isDead,
|
|
@@ -2339,9 +2336,9 @@ function invokeWithRetry(task, run, reporter, logger) {
|
|
|
2339
2336
|
}
|
|
2340
2337
|
var TIMEOUT_SENTINEL = /* @__PURE__ */ Symbol("task-timeout");
|
|
2341
2338
|
var CANCEL_SENTINEL = /* @__PURE__ */ Symbol("task-canceled");
|
|
2342
|
-
async function invokeWithTimeout(
|
|
2343
|
-
const { timeoutMs, cancelGraceMs } =
|
|
2344
|
-
const handler =
|
|
2339
|
+
async function invokeWithTimeout(agent, run, reporter, logger) {
|
|
2340
|
+
const { timeoutMs, cancelGraceMs } = agent.options;
|
|
2341
|
+
const handler = agent.handler;
|
|
2345
2342
|
const invocation = Promise.resolve().then(() => handler(run.params, reporter.context));
|
|
2346
2343
|
const races = [];
|
|
2347
2344
|
if (timeoutMs && timeoutMs > 0) {
|
|
@@ -2398,7 +2395,7 @@ var TaskAgent = class {
|
|
|
2398
2395
|
this.logger = options.logger;
|
|
2399
2396
|
this.api = options.api;
|
|
2400
2397
|
this.client = options.client;
|
|
2401
|
-
this.
|
|
2398
|
+
this.handler = options.handler;
|
|
2402
2399
|
this.tracker = options.tracker;
|
|
2403
2400
|
this.pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
|
|
2404
2401
|
this.backoff = new Backoff(this.logger, "poll", this.pollIntervalMs);
|
|
@@ -2408,7 +2405,7 @@ var TaskAgent = class {
|
|
|
2408
2405
|
pollIntervalMs;
|
|
2409
2406
|
api;
|
|
2410
2407
|
client;
|
|
2411
|
-
|
|
2408
|
+
handler;
|
|
2412
2409
|
tracker;
|
|
2413
2410
|
backoff;
|
|
2414
2411
|
state = "idle";
|
|
@@ -2424,10 +2421,10 @@ var TaskAgent = class {
|
|
|
2424
2421
|
throw new Error("[hobots] start do agente durante o stop \u2014 aguarde o stop terminar");
|
|
2425
2422
|
}
|
|
2426
2423
|
this.state = "started";
|
|
2427
|
-
this.logger.debug(`polling iniciado (
|
|
2424
|
+
this.logger.debug(`polling iniciado (concorr\xEAncia ${this.handler.options.concurrency})`);
|
|
2428
2425
|
this.schedule(0);
|
|
2429
2426
|
}
|
|
2430
|
-
/**
|
|
2427
|
+
/** Antecipa o próximo poll para já. */
|
|
2431
2428
|
wake() {
|
|
2432
2429
|
if (this.state !== "started") return;
|
|
2433
2430
|
if (this.timer) {
|
|
@@ -2458,21 +2455,14 @@ var TaskAgent = class {
|
|
|
2458
2455
|
void this.tick();
|
|
2459
2456
|
}, delayMs);
|
|
2460
2457
|
}
|
|
2461
|
-
/** Slots livres
|
|
2458
|
+
/** Slots livres do agente — vira o `max` do poll (nunca reivindica trabalho para deixar parado). */
|
|
2462
2459
|
freeSlots() {
|
|
2463
|
-
const
|
|
2464
|
-
|
|
2465
|
-
for (const task of this.registry.remoteTasks()) {
|
|
2466
|
-
const free = task.options.concurrency - this.tracker.activeFor(task.slug);
|
|
2467
|
-
if (free <= 0) continue;
|
|
2468
|
-
tasks.push(task.slug);
|
|
2469
|
-
max += free;
|
|
2470
|
-
}
|
|
2471
|
-
return { tasks, max: Math.min(MAX_CLAIM_PER_POLL, max) };
|
|
2460
|
+
const free = this.handler.options.concurrency - this.tracker.activeCount;
|
|
2461
|
+
return Math.min(MAX_CLAIM_PER_POLL, Math.max(0, free));
|
|
2472
2462
|
}
|
|
2473
2463
|
async tick() {
|
|
2474
2464
|
if (this.state !== "started") return;
|
|
2475
|
-
const
|
|
2465
|
+
const max = this.freeSlots();
|
|
2476
2466
|
if (max === 0) {
|
|
2477
2467
|
this.schedule(this.pollIntervalMs);
|
|
2478
2468
|
return;
|
|
@@ -2482,11 +2472,9 @@ var TaskAgent = class {
|
|
|
2482
2472
|
const response = await this.api.poll(
|
|
2483
2473
|
{
|
|
2484
2474
|
// identidade canônica do agente no painel — o mesmo instanceId do heartbeat,
|
|
2485
|
-
// então os dois canais convergem em um agente
|
|
2486
|
-
// tasks (register + start); o filtro `tasks` abaixo só as que este poll pode rodar.
|
|
2475
|
+
// então os dois canais convergem em um agente
|
|
2487
2476
|
instance: this.options.instanceInfo(),
|
|
2488
|
-
max
|
|
2489
|
-
tasks
|
|
2477
|
+
max
|
|
2490
2478
|
},
|
|
2491
2479
|
this.pollAbort.signal
|
|
2492
2480
|
);
|
|
@@ -2510,17 +2498,7 @@ var TaskAgent = class {
|
|
|
2510
2498
|
this.activeRuns.add(promise);
|
|
2511
2499
|
}
|
|
2512
2500
|
async executeClaimed(run) {
|
|
2513
|
-
|
|
2514
|
-
if (!task || task.mode !== "remote") {
|
|
2515
|
-
const tx = new Transaction(this.client, `run:${run.task}`, "hobots.task", run.id);
|
|
2516
|
-
const reporter = new RunReporter(this.api, run.id, run.task, this.logger, tx);
|
|
2517
|
-
reporter.reportFailure(toRunError(Object.assign(new Error(`tarefa n\xE3o registrada no agente: ${run.task}`), { name: "HandlerNotFound" })));
|
|
2518
|
-
await reporter.finalize();
|
|
2519
|
-
tx.setStatus("error");
|
|
2520
|
-
tx.finish();
|
|
2521
|
-
return;
|
|
2522
|
-
}
|
|
2523
|
-
await executeRun({ run, task, api: this.api, client: this.client, logger: this.logger, tracker: this.tracker });
|
|
2501
|
+
await executeRun({ run, handler: this.handler, api: this.api, client: this.client, logger: this.logger, tracker: this.tracker });
|
|
2524
2502
|
}
|
|
2525
2503
|
};
|
|
2526
2504
|
|
|
@@ -2528,11 +2506,11 @@ var TaskAgent = class {
|
|
|
2528
2506
|
var KEY_RE = /^[a-zA-Z0-9_.-]{1,100}$/;
|
|
2529
2507
|
var MAX_COLUMNS = WIRE_LIMITS.MAX_ITEM_COLUMNS;
|
|
2530
2508
|
var MAX_LABEL_CHARS = WIRE_LIMITS.MAX_ITEM_COLUMN_LABEL_CHARS;
|
|
2531
|
-
function normalizeItemColumns(
|
|
2509
|
+
function normalizeItemColumns(input, logger) {
|
|
2532
2510
|
if (input === void 0 || input === null) return void 0;
|
|
2533
2511
|
const columns = input.columns;
|
|
2534
2512
|
if (typeof input !== "object" || !Array.isArray(columns)) {
|
|
2535
|
-
logger.warn(`
|
|
2513
|
+
logger.warn(`items.columns precisa ser uma lista \u2014 declara\xE7\xE3o de colunas ignorada`);
|
|
2536
2514
|
return void 0;
|
|
2537
2515
|
}
|
|
2538
2516
|
const result = [];
|
|
@@ -2542,15 +2520,15 @@ function normalizeItemColumns(slug, input, logger) {
|
|
|
2542
2520
|
const rawLabel = typeof entry === "object" && entry !== null ? entry.label : void 0;
|
|
2543
2521
|
const key = typeof rawKey === "string" ? rawKey.trim() : "";
|
|
2544
2522
|
if (!KEY_RE.test(key)) {
|
|
2545
|
-
logger.warn(`
|
|
2523
|
+
logger.warn(`coluna de item inv\xE1lida ignorada (${JSON.stringify(rawKey)}) \u2014 use letras, n\xFAmeros, ponto, h\xEDfen e underline`);
|
|
2546
2524
|
continue;
|
|
2547
2525
|
}
|
|
2548
2526
|
if (seen.has(key)) {
|
|
2549
|
-
logger.warn(`
|
|
2527
|
+
logger.warn(`coluna de item "${key}" repetida ignorada`);
|
|
2550
2528
|
continue;
|
|
2551
2529
|
}
|
|
2552
2530
|
if (result.length >= MAX_COLUMNS) {
|
|
2553
|
-
logger.warn(`
|
|
2531
|
+
logger.warn(`no m\xE1ximo ${MAX_COLUMNS} colunas de item \u2014 "${key}" em diante ignoradas`);
|
|
2554
2532
|
break;
|
|
2555
2533
|
}
|
|
2556
2534
|
seen.add(key);
|
|
@@ -2562,15 +2540,15 @@ function normalizeItemColumns(slug, input, logger) {
|
|
|
2562
2540
|
|
|
2563
2541
|
// src/tasks/local.ts
|
|
2564
2542
|
var LocalRunner = class {
|
|
2565
|
-
constructor(
|
|
2566
|
-
this.
|
|
2543
|
+
constructor(handler, deps) {
|
|
2544
|
+
this.handler = handler;
|
|
2567
2545
|
this.deps = deps;
|
|
2568
|
-
this.backoff = new Backoff(deps.logger,
|
|
2546
|
+
this.backoff = new Backoff(deps.logger, "cria\xE7\xE3o da run");
|
|
2569
2547
|
this.done = new Promise((resolve2) => {
|
|
2570
2548
|
this.resolveDone = resolve2;
|
|
2571
2549
|
});
|
|
2572
2550
|
}
|
|
2573
|
-
|
|
2551
|
+
handler;
|
|
2574
2552
|
deps;
|
|
2575
2553
|
abort = new AbortController();
|
|
2576
2554
|
backoff;
|
|
@@ -2581,17 +2559,17 @@ var LocalRunner = class {
|
|
|
2581
2559
|
try {
|
|
2582
2560
|
const run = await this.createRun();
|
|
2583
2561
|
if (!run) return;
|
|
2584
|
-
this.deps.logger.debug(`run ${run.id} aberta
|
|
2562
|
+
this.deps.logger.debug(`run ${run.id} aberta (modo start)`);
|
|
2585
2563
|
await executeRun({
|
|
2586
2564
|
run,
|
|
2587
|
-
|
|
2565
|
+
handler: this.handler,
|
|
2588
2566
|
api: this.deps.api,
|
|
2589
2567
|
client: this.deps.client,
|
|
2590
2568
|
logger: this.deps.logger,
|
|
2591
2569
|
tracker: this.deps.tracker
|
|
2592
2570
|
});
|
|
2593
2571
|
} catch (error) {
|
|
2594
|
-
this.deps.logger.error(
|
|
2572
|
+
this.deps.logger.error("erro inesperado na execu\xE7\xE3o do agente:", error);
|
|
2595
2573
|
} finally {
|
|
2596
2574
|
this.resolveDone();
|
|
2597
2575
|
}
|
|
@@ -2604,7 +2582,7 @@ var LocalRunner = class {
|
|
|
2604
2582
|
for (; ; ) {
|
|
2605
2583
|
if (this.abort.signal.aborted) return void 0;
|
|
2606
2584
|
try {
|
|
2607
|
-
return await this.deps.api.createRun({
|
|
2585
|
+
return await this.deps.api.createRun({ instance: this.deps.instanceInfo() }, this.abort.signal);
|
|
2608
2586
|
} catch (error) {
|
|
2609
2587
|
if (this.abort.signal.aborted) return void 0;
|
|
2610
2588
|
await abortableSleep(this.backoff.next(error), this.abort.signal);
|
|
@@ -2614,45 +2592,6 @@ var LocalRunner = class {
|
|
|
2614
2592
|
};
|
|
2615
2593
|
|
|
2616
2594
|
// src/tasks/registry.ts
|
|
2617
|
-
var TaskRegistry = class {
|
|
2618
|
-
tasks = /* @__PURE__ */ new Map();
|
|
2619
|
-
add(task) {
|
|
2620
|
-
if (this.tasks.has(task.slug)) {
|
|
2621
|
-
throw new Error(`[hobots] task "${task.slug}" j\xE1 registrada \u2014 cada slug s\xF3 pode ter um handler (register ou start)`);
|
|
2622
|
-
}
|
|
2623
|
-
this.tasks.set(task.slug, task);
|
|
2624
|
-
}
|
|
2625
|
-
get(slug) {
|
|
2626
|
-
return this.tasks.get(slug);
|
|
2627
|
-
}
|
|
2628
|
-
get size() {
|
|
2629
|
-
return this.tasks.size;
|
|
2630
|
-
}
|
|
2631
|
-
/** Tasks que chegam pelo poll (register). */
|
|
2632
|
-
remoteTasks() {
|
|
2633
|
-
return [...this.tasks.values()].filter((task) => task.mode === "remote");
|
|
2634
|
-
}
|
|
2635
|
-
/** Tasks abertas pelo próprio bot (start). */
|
|
2636
|
-
localTasks() {
|
|
2637
|
-
return [...this.tasks.values()].filter((task) => task.mode === "local");
|
|
2638
|
-
}
|
|
2639
|
-
/** Todos os slugs, para a presença da instância (`instance.tasks`). */
|
|
2640
|
-
allSlugs() {
|
|
2641
|
-
return [...this.tasks.keys()];
|
|
2642
|
-
}
|
|
2643
|
-
/** Colunas de itens declaradas no código, por slug (`instance.task_items`); undefined se nenhuma task declarou. */
|
|
2644
|
-
itemDeclarations() {
|
|
2645
|
-
let result;
|
|
2646
|
-
for (const task of this.tasks.values()) {
|
|
2647
|
-
if (!task.options.itemColumns) continue;
|
|
2648
|
-
(result ??= {})[task.slug] = { attributes: task.options.itemColumns };
|
|
2649
|
-
}
|
|
2650
|
-
return result;
|
|
2651
|
-
}
|
|
2652
|
-
clear() {
|
|
2653
|
-
this.tasks.clear();
|
|
2654
|
-
}
|
|
2655
|
-
};
|
|
2656
2595
|
var RunTracker = class {
|
|
2657
2596
|
constructor(api) {
|
|
2658
2597
|
this.api = api;
|
|
@@ -2673,12 +2612,6 @@ var RunTracker = class {
|
|
|
2673
2612
|
get activeCount() {
|
|
2674
2613
|
return this.reporters.size;
|
|
2675
2614
|
}
|
|
2676
|
-
/** Runs ativas de uma task (concorrência por task no poll). */
|
|
2677
|
-
activeFor(slug) {
|
|
2678
|
-
let count = 0;
|
|
2679
|
-
for (const reporter of this.reporters) if (reporter.context.task === slug) count++;
|
|
2680
|
-
return count;
|
|
2681
|
-
}
|
|
2682
2615
|
/** A única run ativa, quando há exatamente uma — o que `hobots.*` usa fora de um contexto async. */
|
|
2683
2616
|
singleActive() {
|
|
2684
2617
|
if (this.reporters.size !== 1) return void 0;
|
|
@@ -2767,40 +2700,13 @@ var DEFAULT_RETRIES2 = 1;
|
|
|
2767
2700
|
var DEFAULT_RETRY_DELAY_MS2 = 5e3;
|
|
2768
2701
|
var DEFAULT_CANCEL_GRACE_MS = 1e4;
|
|
2769
2702
|
var MAX_CONCURRENCY = 5;
|
|
2770
|
-
var MAX_SLUG_CHARS = 255;
|
|
2771
|
-
var PROCESS_LEVEL_KEYS = [
|
|
2772
|
-
"clientSecret",
|
|
2773
|
-
"instanceId",
|
|
2774
|
-
"environment",
|
|
2775
|
-
"release",
|
|
2776
|
-
"tags",
|
|
2777
|
-
"debug",
|
|
2778
|
-
"machineMetrics",
|
|
2779
|
-
"captureUnhandled",
|
|
2780
|
-
"installExitHandlers"
|
|
2781
|
-
];
|
|
2782
2703
|
var runtime;
|
|
2783
2704
|
var warnedNoRuntime = false;
|
|
2784
2705
|
function hostname() {
|
|
2785
2706
|
if (!isNode()) return void 0;
|
|
2786
2707
|
return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
|
|
2787
2708
|
}
|
|
2788
|
-
function
|
|
2789
|
-
if (a === b) return true;
|
|
2790
|
-
if (a && b && typeof a === "object" && typeof b === "object") return JSON.stringify(a) === JSON.stringify(b);
|
|
2791
|
-
return false;
|
|
2792
|
-
}
|
|
2793
|
-
function ensureRuntime(options) {
|
|
2794
|
-
if (runtime) {
|
|
2795
|
-
for (const key of PROCESS_LEVEL_KEYS) {
|
|
2796
|
-
if (options[key] !== void 0 && !sameValue(options[key], runtime.options[key])) {
|
|
2797
|
-
runtime.logger.warn(
|
|
2798
|
-
`op\xE7\xE3o "${key}" divergente ignorada \u2014 o primeiro register()/start() define as op\xE7\xF5es do processo`
|
|
2799
|
-
);
|
|
2800
|
-
}
|
|
2801
|
-
}
|
|
2802
|
-
return runtime;
|
|
2803
|
-
}
|
|
2709
|
+
function createRuntime(options, handler) {
|
|
2804
2710
|
const config = resolveConfig({ clientSecret: options.clientSecret });
|
|
2805
2711
|
const logger = makeLogger(options.debug ?? false);
|
|
2806
2712
|
const environment = resolveEnvironmentOption(options.environment);
|
|
@@ -2808,19 +2714,19 @@ function ensureRuntime(options) {
|
|
|
2808
2714
|
{ environment, release: options.release, tags: options.tags, debug: options.debug },
|
|
2809
2715
|
config
|
|
2810
2716
|
);
|
|
2811
|
-
const api = new TasksApi({ baseUrl: config.
|
|
2812
|
-
|
|
2717
|
+
const api = new TasksApi({ baseUrl: config.runsBaseUrl, key: config.key, logger });
|
|
2718
|
+
const rt = {
|
|
2813
2719
|
options: { ...options, clientSecret: void 0 },
|
|
2814
2720
|
config,
|
|
2815
2721
|
client,
|
|
2816
2722
|
logger,
|
|
2817
2723
|
api,
|
|
2818
|
-
|
|
2724
|
+
handler,
|
|
2819
2725
|
tracker: new RunTracker(api),
|
|
2820
2726
|
sampler: void 0,
|
|
2821
2727
|
heartbeat: void 0,
|
|
2822
2728
|
agent: void 0,
|
|
2823
|
-
|
|
2729
|
+
local: void 0,
|
|
2824
2730
|
instanceId: resolveInstanceId(options.instanceId),
|
|
2825
2731
|
environment,
|
|
2826
2732
|
pollIntervalMs: void 0,
|
|
@@ -2830,24 +2736,19 @@ function ensureRuntime(options) {
|
|
|
2830
2736
|
uninstallGlobalHandlers: options.captureUnhandled !== false ? installGlobalHandlers(client) : void 0,
|
|
2831
2737
|
uninstallShutdown: void 0
|
|
2832
2738
|
};
|
|
2833
|
-
runtime.options.clientSecret = options.clientSecret;
|
|
2834
2739
|
logger.debug("inicializado", { endpoint: config.ingestEndpoint });
|
|
2835
|
-
return
|
|
2740
|
+
return rt;
|
|
2836
2741
|
}
|
|
2837
|
-
function
|
|
2838
|
-
if (typeof slug !== "string" || slug.trim().length === 0 || slug.length > MAX_SLUG_CHARS) {
|
|
2839
|
-
throw new Error(`[hobots] ${fn}(): o slug da task precisa ser uma string de 1 a ${MAX_SLUG_CHARS} caracteres`);
|
|
2840
|
-
}
|
|
2742
|
+
function validateHandler(handler, fn) {
|
|
2841
2743
|
if (typeof handler !== "function") {
|
|
2842
|
-
throw new Error(`[hobots] ${fn}(
|
|
2744
|
+
throw new Error(`[hobots] ${fn}(): o handler precisa ser uma fun\xE7\xE3o \u2014 ${fn}(async (params) => { ... })`);
|
|
2843
2745
|
}
|
|
2844
|
-
return slug.trim();
|
|
2845
2746
|
}
|
|
2846
2747
|
function clampConcurrency(value) {
|
|
2847
2748
|
if (value === void 0 || !Number.isFinite(value)) return 1;
|
|
2848
2749
|
return Math.min(MAX_CONCURRENCY, Math.max(1, Math.floor(value)));
|
|
2849
2750
|
}
|
|
2850
|
-
function
|
|
2751
|
+
function resolveRunOptions(options, mode, logger) {
|
|
2851
2752
|
const retries = options.retries === void 0 || !Number.isFinite(options.retries) ? DEFAULT_RETRIES2 : Math.max(0, Math.floor(options.retries));
|
|
2852
2753
|
return {
|
|
2853
2754
|
retries,
|
|
@@ -2857,7 +2758,7 @@ function resolveTaskOptions(slug, options, mode, logger) {
|
|
|
2857
2758
|
logFlushIntervalMs: options.logFlushIntervalMs,
|
|
2858
2759
|
cancelCheckIntervalMs: options.cancelCheckIntervalMs,
|
|
2859
2760
|
cancelGraceMs: options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS,
|
|
2860
|
-
itemColumns: normalizeItemColumns(
|
|
2761
|
+
itemColumns: normalizeItemColumns(options.items, logger)
|
|
2861
2762
|
};
|
|
2862
2763
|
}
|
|
2863
2764
|
function instanceInfo(rt) {
|
|
@@ -2867,10 +2768,10 @@ function instanceInfo(rt) {
|
|
|
2867
2768
|
host: hostname(),
|
|
2868
2769
|
// ambiente da instância: junto do heartbeat, é como o servidor sabe onde o robô roda
|
|
2869
2770
|
environment: rt.environment,
|
|
2870
|
-
//
|
|
2871
|
-
|
|
2771
|
+
// como esta instância trabalha — o painel mostra "sob demanda" / "sempre ligado"
|
|
2772
|
+
mode: rt.handler.mode === "remote" ? "on_demand" : "always_on",
|
|
2872
2773
|
// colunas da aba "itens" declaradas no código — a plataforma usa enquanto o usuário não personalizar
|
|
2873
|
-
|
|
2774
|
+
items: rt.handler.options.itemColumns ? { attributes: rt.handler.options.itemColumns } : void 0,
|
|
2874
2775
|
// o servidor calcula a tolerância de offline (3×) a partir disto em agentes que fazem
|
|
2875
2776
|
// poll; clampado à faixa aceita pelo schema (1s..15min)
|
|
2876
2777
|
poll_interval_ms: rt.agent ? Math.min(Math.max(rt.agent.pollIntervalMs, 1e3), 9e5) : void 0,
|
|
@@ -2883,22 +2784,22 @@ function ensureAgent(rt) {
|
|
|
2883
2784
|
api: rt.api,
|
|
2884
2785
|
client: rt.client,
|
|
2885
2786
|
logger: rt.logger,
|
|
2886
|
-
|
|
2787
|
+
handler: rt.handler,
|
|
2887
2788
|
tracker: rt.tracker,
|
|
2888
2789
|
instanceInfo: () => instanceInfo(rt),
|
|
2889
2790
|
pollIntervalMs: rt.pollIntervalMs
|
|
2890
2791
|
});
|
|
2891
2792
|
return rt.agent;
|
|
2892
2793
|
}
|
|
2893
|
-
function launchLocal(rt
|
|
2894
|
-
const runner = new LocalRunner(
|
|
2794
|
+
function launchLocal(rt) {
|
|
2795
|
+
const runner = new LocalRunner(rt.handler, {
|
|
2895
2796
|
api: rt.api,
|
|
2896
2797
|
client: rt.client,
|
|
2897
2798
|
logger: rt.logger,
|
|
2898
2799
|
tracker: rt.tracker,
|
|
2899
2800
|
instanceInfo: () => instanceInfo(rt)
|
|
2900
2801
|
});
|
|
2901
|
-
rt.
|
|
2802
|
+
rt.local = runner;
|
|
2902
2803
|
void runner.run();
|
|
2903
2804
|
}
|
|
2904
2805
|
async function boot(rt) {
|
|
@@ -2920,8 +2821,8 @@ async function boot(rt) {
|
|
|
2920
2821
|
if (rt.options.installExitHandlers !== false) {
|
|
2921
2822
|
rt.uninstallShutdown = installShutdownHandlers(rt.tracker);
|
|
2922
2823
|
}
|
|
2923
|
-
if (rt.
|
|
2924
|
-
|
|
2824
|
+
if (rt.handler.mode === "remote") ensureAgent(rt).start();
|
|
2825
|
+
else launchLocal(rt);
|
|
2925
2826
|
}
|
|
2926
2827
|
function scheduleBoot(rt) {
|
|
2927
2828
|
if (rt.bootScheduled) return;
|
|
@@ -2930,34 +2831,23 @@ function scheduleBoot(rt) {
|
|
|
2930
2831
|
rt.bootPromise = boot(rt).catch((error) => rt.logger.error("falha ao iniciar o SDK:", error));
|
|
2931
2832
|
});
|
|
2932
2833
|
}
|
|
2933
|
-
function
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
if (rt.pollIntervalMs === void 0 && !rt.agent) rt.pollIntervalMs = options.pollIntervalMs;
|
|
2938
|
-
else if (rt.pollIntervalMs !== options.pollIntervalMs) {
|
|
2939
|
-
rt.logger.warn('op\xE7\xE3o "pollIntervalMs" divergente ignorada \u2014 o primeiro register() define o intervalo do poll');
|
|
2940
|
-
}
|
|
2941
|
-
}
|
|
2942
|
-
const registered = { slug, mode, handler, options: resolveTaskOptions(slug, options, mode, rt.logger) };
|
|
2943
|
-
rt.registry.add(registered);
|
|
2944
|
-
if (!rt.booted) {
|
|
2945
|
-
scheduleBoot(rt);
|
|
2946
|
-
return;
|
|
2947
|
-
}
|
|
2948
|
-
if (mode === "remote") {
|
|
2949
|
-
const agent = ensureAgent(rt);
|
|
2950
|
-
agent.start();
|
|
2951
|
-
agent.wake();
|
|
2952
|
-
} else {
|
|
2953
|
-
launchLocal(rt, registered);
|
|
2834
|
+
function startAgent(fn, mode, handler, options) {
|
|
2835
|
+
validateHandler(handler, fn);
|
|
2836
|
+
if (runtime) {
|
|
2837
|
+
throw new Error(`[hobots] ${fn}(): o agente j\xE1 foi iniciado \u2014 register()/start() s\xF3 pode ser chamado uma vez por processo (chame hobots.close() antes de iniciar de novo)`);
|
|
2954
2838
|
}
|
|
2839
|
+
const logger = makeLogger(options.debug ?? false);
|
|
2840
|
+
const agentHandler = { mode, handler, options: resolveRunOptions(options, mode, logger) };
|
|
2841
|
+
const rt = createRuntime(options, agentHandler);
|
|
2842
|
+
if (mode === "remote" && options.pollIntervalMs !== void 0) rt.pollIntervalMs = options.pollIntervalMs;
|
|
2843
|
+
runtime = rt;
|
|
2844
|
+
scheduleBoot(rt);
|
|
2955
2845
|
}
|
|
2956
|
-
function register(
|
|
2957
|
-
|
|
2846
|
+
function register(handler, options = {}) {
|
|
2847
|
+
startAgent("register", "remote", handler, options);
|
|
2958
2848
|
}
|
|
2959
|
-
function start(
|
|
2960
|
-
|
|
2849
|
+
function start(handler, options = {}) {
|
|
2850
|
+
startAgent("start", "local", handler, options);
|
|
2961
2851
|
}
|
|
2962
2852
|
function currentReporter() {
|
|
2963
2853
|
return currentRunReporter() ?? runtime?.tracker.singleActive();
|
|
@@ -2971,7 +2861,7 @@ function toRunLogLevel(type) {
|
|
|
2971
2861
|
function forwardAttachment(rt, attachment, message, type) {
|
|
2972
2862
|
const reporter = currentReporter();
|
|
2973
2863
|
if (!reporter) {
|
|
2974
|
-
rt.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o
|
|
2864
|
+
rt.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o");
|
|
2975
2865
|
return;
|
|
2976
2866
|
}
|
|
2977
2867
|
const withMeta = typeof attachment === "object" && !isImageAttachment(attachment) && "image" in attachment ? image(attachment.image, { caption: attachment.caption, contentType: attachment.contentType }) : attachment;
|
|
@@ -2982,7 +2872,7 @@ function captureException(error, hint) {
|
|
|
2982
2872
|
if (!rt) {
|
|
2983
2873
|
if (!warnedNoRuntime) {
|
|
2984
2874
|
warnedNoRuntime = true;
|
|
2985
|
-
console.warn("[hobots] captureException antes de
|
|
2875
|
+
console.warn("[hobots] captureException antes de iniciar o agente (register/start) \u2014 evento descartado");
|
|
2986
2876
|
}
|
|
2987
2877
|
return void 0;
|
|
2988
2878
|
}
|
|
@@ -2998,7 +2888,7 @@ async function close(timeoutMs = 3e4) {
|
|
|
2998
2888
|
runtime = void 0;
|
|
2999
2889
|
await rt.bootPromise;
|
|
3000
2890
|
if (rt.agent) await rt.agent.stop(0);
|
|
3001
|
-
|
|
2891
|
+
rt.local?.stop();
|
|
3002
2892
|
const idle = await rt.tracker.waitIdle(timeoutMs);
|
|
3003
2893
|
if (!idle) {
|
|
3004
2894
|
rt.logger.warn(`${rt.tracker.activeCount} run(s) ainda em execu\xE7\xE3o ap\xF3s ${timeoutMs}ms \u2014 abandonando a espera`);
|
|
@@ -3016,15 +2906,14 @@ var warned = /* @__PURE__ */ new Set();
|
|
|
3016
2906
|
function warnOutsideRun(member) {
|
|
3017
2907
|
if (warned.has(member)) return;
|
|
3018
2908
|
warned.add(member);
|
|
3019
|
-
const message = `hobots.${member} usado fora de uma execu\xE7\xE3o
|
|
2909
|
+
const message = `hobots.${member} usado fora de uma execu\xE7\xE3o \u2014 ignorado (inicie o agente com register()/start() e use dentro do handler)`;
|
|
3020
2910
|
const logger = activeLogger();
|
|
3021
2911
|
if (logger) logger.warn(message);
|
|
3022
2912
|
else console.warn(`[hobots] ${message}`);
|
|
3023
2913
|
}
|
|
3024
|
-
var FALLBACK_TASK = "hobots";
|
|
3025
2914
|
var fallbackLogAt = (level) => (message, ...rest) => {
|
|
3026
2915
|
const { text, image: dropped } = splitLogArgs(message, rest);
|
|
3027
|
-
printRunLog(
|
|
2916
|
+
printRunLog(level, dropped ? `${text} [imagem descartada]` : text);
|
|
3028
2917
|
};
|
|
3029
2918
|
var fallbackLog = {
|
|
3030
2919
|
debug: fallbackLogAt("debug"),
|
|
@@ -3041,7 +2930,7 @@ var fallbackEachSink = {
|
|
|
3041
2930
|
advance: noop,
|
|
3042
2931
|
checkCanceled: noop,
|
|
3043
2932
|
isAborted: () => false,
|
|
3044
|
-
logError: (message) => printRunLog(
|
|
2933
|
+
logError: (message) => printRunLog("error", message)
|
|
3045
2934
|
};
|
|
3046
2935
|
var fallbackItems = {
|
|
3047
2936
|
add: noop,
|
|
@@ -3068,7 +2957,6 @@ function method(value) {
|
|
|
3068
2957
|
var hobots = Object.defineProperties({}, {
|
|
3069
2958
|
// ── contexto da run atual (getters) ─────────────────────────────────────────
|
|
3070
2959
|
runId: getter("runId", () => ""),
|
|
3071
|
-
task: getter("task", () => ""),
|
|
3072
2960
|
params: getter("params", () => ({})),
|
|
3073
2961
|
log: getter("log", () => fallbackLog),
|
|
3074
2962
|
items: getter("items", () => fallbackItems),
|
|
@@ -3082,12 +2970,12 @@ var hobots = Object.defineProperties({}, {
|
|
|
3082
2970
|
return reporter ? reporter.context.retry(fn, options) : retryWithBackoff(fn, options);
|
|
3083
2971
|
}),
|
|
3084
2972
|
// ── processo ────────────────────────────────────────────────────────────────
|
|
3085
|
-
register: method((
|
|
3086
|
-
register(
|
|
2973
|
+
register: method((handler, options) => {
|
|
2974
|
+
register(handler, options);
|
|
3087
2975
|
installGlobalHobots();
|
|
3088
2976
|
}),
|
|
3089
|
-
start: method((
|
|
3090
|
-
start(
|
|
2977
|
+
start: method((handler, options) => {
|
|
2978
|
+
start(handler, options);
|
|
3091
2979
|
installGlobalHobots();
|
|
3092
2980
|
}),
|
|
3093
2981
|
close: method((timeoutMs) => close(timeoutMs)),
|