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.cjs
CHANGED
|
@@ -96,7 +96,7 @@ function scrubSecrets(text) {
|
|
|
96
96
|
|
|
97
97
|
// src/utils.ts
|
|
98
98
|
var SDK_NAME = "hobots";
|
|
99
|
-
var SDK_VERSION = true ? "
|
|
99
|
+
var SDK_VERSION = true ? "4.0.0" : "0.0.0-dev";
|
|
100
100
|
function isNode() {
|
|
101
101
|
return typeof process !== "undefined" && typeof process.versions?.node === "string";
|
|
102
102
|
}
|
|
@@ -580,7 +580,7 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
580
580
|
const origin = fromOption ? "clientSecret" : `env ${CLIENT_SECRET_ENV}`;
|
|
581
581
|
if (!key) {
|
|
582
582
|
throw new Error(
|
|
583
|
-
`${prefix} Client Secret ausente \u2014 defina a env ${CLIENT_SECRET_ENV} (ou passe clientSecret nas op\xE7\xF5es
|
|
583
|
+
`${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`
|
|
584
584
|
);
|
|
585
585
|
}
|
|
586
586
|
if (!SECRET_PATTERN.test(key)) {
|
|
@@ -593,7 +593,7 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
593
593
|
key,
|
|
594
594
|
baseUrl,
|
|
595
595
|
ingestEndpoint: `${baseUrl}/v1/ingest`,
|
|
596
|
-
|
|
596
|
+
runsBaseUrl: `${baseUrl}/v1/runs`
|
|
597
597
|
};
|
|
598
598
|
}
|
|
599
599
|
|
|
@@ -957,7 +957,7 @@ var TasksApi = class {
|
|
|
957
957
|
const { signal: timeoutSignal, cleanup } = withTimeoutSignal(UPDATE_TIMEOUT_MS, signal);
|
|
958
958
|
let response;
|
|
959
959
|
try {
|
|
960
|
-
response = await fetch(`${this.baseUrl}
|
|
960
|
+
response = await fetch(`${this.baseUrl}`, {
|
|
961
961
|
redirect: "error",
|
|
962
962
|
method: "POST",
|
|
963
963
|
headers: this.headers(),
|
|
@@ -971,12 +971,11 @@ var TasksApi = class {
|
|
|
971
971
|
throw new TasksApiError(`cria\xE7\xE3o da run falhou (HTTP ${response.status})`, response.status);
|
|
972
972
|
}
|
|
973
973
|
const body = await response.json().catch(() => null);
|
|
974
|
-
if (typeof body?.id !== "string"
|
|
975
|
-
throw new TasksApiError("resposta inv\xE1lida da cria\xE7\xE3o de run (id
|
|
974
|
+
if (typeof body?.id !== "string") {
|
|
975
|
+
throw new TasksApiError("resposta inv\xE1lida da cria\xE7\xE3o de run (id ausente)", 0);
|
|
976
976
|
}
|
|
977
977
|
return {
|
|
978
978
|
id: body.id,
|
|
979
|
-
task: body.task,
|
|
980
979
|
params: body.params && typeof body.params === "object" && !Array.isArray(body.params) ? body.params : {},
|
|
981
980
|
form: null,
|
|
982
981
|
source: typeof body.source === "string" ? body.source : "agent",
|
|
@@ -991,9 +990,9 @@ var TasksApi = class {
|
|
|
991
990
|
}
|
|
992
991
|
const valid = [];
|
|
993
992
|
for (const run of runs.slice(0, Math.max(0, max))) {
|
|
994
|
-
if (typeof run?.id !== "string"
|
|
993
|
+
if (typeof run?.id !== "string") continue;
|
|
995
994
|
const params = run.params && typeof run.params === "object" && !Array.isArray(run.params) ? run.params : {};
|
|
996
|
-
valid.push({ ...run, id: run.id,
|
|
995
|
+
valid.push({ ...run, id: run.id, params });
|
|
997
996
|
}
|
|
998
997
|
if (valid.length < runs.length) {
|
|
999
998
|
this.logger.warn(`poll retornou ${runs.length - valid.length} run(s) inv\xE1lida(s)/excedente(s) \u2014 descartadas`);
|
|
@@ -1008,7 +1007,7 @@ var TasksApi = class {
|
|
|
1008
1007
|
let response;
|
|
1009
1008
|
try {
|
|
1010
1009
|
response = await fetchWithRetry(
|
|
1011
|
-
() => fetch(`${this.baseUrl}
|
|
1010
|
+
() => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}`, {
|
|
1012
1011
|
redirect: "error",
|
|
1013
1012
|
method: "PATCH",
|
|
1014
1013
|
headers: this.headers(),
|
|
@@ -1030,7 +1029,7 @@ var TasksApi = class {
|
|
|
1030
1029
|
* que a run já foi cancelada/finalizada, o que é aceitável nesse caminho.
|
|
1031
1030
|
*/
|
|
1032
1031
|
async updateRunOnce(runId, update, timeoutMs = 2e3) {
|
|
1033
|
-
await fetch(`${this.baseUrl}
|
|
1032
|
+
await fetch(`${this.baseUrl}/${encodeURIComponent(runId)}`, {
|
|
1034
1033
|
redirect: "error",
|
|
1035
1034
|
method: "PATCH",
|
|
1036
1035
|
headers: this.headers(),
|
|
@@ -1047,7 +1046,7 @@ var TasksApi = class {
|
|
|
1047
1046
|
let response;
|
|
1048
1047
|
try {
|
|
1049
1048
|
response = await fetchWithRetry(
|
|
1050
|
-
() => fetch(`${this.baseUrl}
|
|
1049
|
+
() => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/items`, {
|
|
1051
1050
|
redirect: "error",
|
|
1052
1051
|
method: "POST",
|
|
1053
1052
|
headers: this.headers(),
|
|
@@ -1077,7 +1076,7 @@ var TasksApi = class {
|
|
|
1077
1076
|
() => withRequestTimeout(
|
|
1078
1077
|
UPDATE_TIMEOUT_MS,
|
|
1079
1078
|
signal,
|
|
1080
|
-
(timeoutSignal) => fetch(`${this.baseUrl}
|
|
1079
|
+
(timeoutSignal) => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/attachments`, {
|
|
1081
1080
|
redirect: "error",
|
|
1082
1081
|
method: "POST",
|
|
1083
1082
|
headers: this.headers(),
|
|
@@ -1128,7 +1127,7 @@ var TasksApi = class {
|
|
|
1128
1127
|
/** Marca o anexo como enviado (ou falho). Best-effort — a leitura não depende disso. */
|
|
1129
1128
|
async confirmAttachment(runId, attachmentId, request) {
|
|
1130
1129
|
const response = await fetch(
|
|
1131
|
-
`${this.baseUrl}
|
|
1130
|
+
`${this.baseUrl}/${encodeURIComponent(runId)}/attachments/${encodeURIComponent(attachmentId)}`,
|
|
1132
1131
|
{
|
|
1133
1132
|
redirect: "error",
|
|
1134
1133
|
method: "PATCH",
|
|
@@ -1386,8 +1385,8 @@ function splitLogArgs(message, rest) {
|
|
|
1386
1385
|
if (text.length === 0) return { text: last.caption !== void 0 ? safeString(last.caption) : "captura de tela", image: last };
|
|
1387
1386
|
return { text: joinLogArgs(text[0], text.slice(1)), image: last };
|
|
1388
1387
|
}
|
|
1389
|
-
function printRunLog(
|
|
1390
|
-
const line = colorize(`[hobots
|
|
1388
|
+
function printRunLog(level, message) {
|
|
1389
|
+
const line = colorize(`[hobots] ${level.toUpperCase()}`, LOG_LEVEL_COLOR[level]);
|
|
1391
1390
|
const method2 = level === "warning" ? "warn" : level === "success" ? "info" : level;
|
|
1392
1391
|
console[method2](line, message);
|
|
1393
1392
|
}
|
|
@@ -1433,7 +1432,7 @@ function scrubPayloadValue(value, depth) {
|
|
|
1433
1432
|
return out;
|
|
1434
1433
|
}
|
|
1435
1434
|
var RunReporter = class {
|
|
1436
|
-
constructor(api, runId,
|
|
1435
|
+
constructor(api, runId, logger, tx, flushIntervalMs, cancelCheckIntervalMs, params = {}) {
|
|
1437
1436
|
this.api = api;
|
|
1438
1437
|
this.runId = runId;
|
|
1439
1438
|
this.logger = logger;
|
|
@@ -1515,7 +1514,6 @@ var RunReporter = class {
|
|
|
1515
1514
|
const sleep2 = (ms) => cancelableSleep(ms, signal);
|
|
1516
1515
|
this.context = {
|
|
1517
1516
|
runId,
|
|
1518
|
-
task,
|
|
1519
1517
|
params,
|
|
1520
1518
|
log,
|
|
1521
1519
|
items,
|
|
@@ -1624,7 +1622,7 @@ var RunReporter = class {
|
|
|
1624
1622
|
const scrubbed = scrubSecrets(safeString(message));
|
|
1625
1623
|
const ts = nowISO();
|
|
1626
1624
|
const attachmentId = image3 === void 0 ? void 0 : this.queueAttachment(image3, ts);
|
|
1627
|
-
printRunLog(
|
|
1625
|
+
printRunLog(level, attachmentId ? `${scrubbed} [imagem]` : scrubbed);
|
|
1628
1626
|
this.push(level, scrubbed, true, attachmentId ? [attachmentId] : void 0, ts);
|
|
1629
1627
|
}
|
|
1630
1628
|
/** Bufferiza uma entrada de log sem eco no terminal (cap, truncate, flush). */
|
|
@@ -2269,7 +2267,7 @@ var Transaction = class {
|
|
|
2269
2267
|
if (this.attachmentSink) return this.attachmentSink(image3, nowISO());
|
|
2270
2268
|
if (!this.warnedNoAttachmentSink) {
|
|
2271
2269
|
this.warnedNoAttachmentSink = true;
|
|
2272
|
-
console.warn("[hobots] imagem de step descartada \u2014 s\xF3 \xE9 suportada dentro de uma execu\xE7\xE3o
|
|
2270
|
+
console.warn("[hobots] imagem de step descartada \u2014 s\xF3 \xE9 suportada dentro de uma execu\xE7\xE3o");
|
|
2273
2271
|
}
|
|
2274
2272
|
return void 0;
|
|
2275
2273
|
}
|
|
@@ -2335,13 +2333,12 @@ var Transaction = class {
|
|
|
2335
2333
|
// src/tasks/runner.ts
|
|
2336
2334
|
var DEFAULT_RETRY_DELAY_MS = 5e3;
|
|
2337
2335
|
async function executeRun(input) {
|
|
2338
|
-
const { run,
|
|
2339
|
-
const options =
|
|
2340
|
-
const tx = new Transaction(client,
|
|
2336
|
+
const { run, handler, api, client, logger, tracker } = input;
|
|
2337
|
+
const options = handler.options;
|
|
2338
|
+
const tx = new Transaction(client, "run", "hobots.run", run.id);
|
|
2341
2339
|
const reporter = new RunReporter(
|
|
2342
2340
|
api,
|
|
2343
2341
|
run.id,
|
|
2344
|
-
run.task,
|
|
2345
2342
|
logger,
|
|
2346
2343
|
tx,
|
|
2347
2344
|
options.logFlushIntervalMs,
|
|
@@ -2357,9 +2354,9 @@ async function executeRun(input) {
|
|
|
2357
2354
|
tx.finish();
|
|
2358
2355
|
return;
|
|
2359
2356
|
}
|
|
2360
|
-
logger.debug(`executando run ${run.id}
|
|
2357
|
+
logger.debug(`executando run ${run.id}`);
|
|
2361
2358
|
try {
|
|
2362
|
-
await runWithRunContext({ reporter }, () => invokeWithRetry(
|
|
2359
|
+
await runWithRunContext({ reporter }, () => invokeWithRetry(handler, run, reporter, logger));
|
|
2363
2360
|
await reporter.finalizeAttachments();
|
|
2364
2361
|
await reporter.finalizeItems();
|
|
2365
2362
|
warnIncompleteProgress(reporter);
|
|
@@ -2387,11 +2384,11 @@ function warnIncompleteProgress(reporter) {
|
|
|
2387
2384
|
`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`
|
|
2388
2385
|
);
|
|
2389
2386
|
}
|
|
2390
|
-
function invokeWithRetry(
|
|
2391
|
-
const { retries, retryDelayMs } =
|
|
2387
|
+
function invokeWithRetry(agent, run, reporter, logger) {
|
|
2388
|
+
const { retries, retryDelayMs } = agent.options;
|
|
2392
2389
|
const total = retries + 1;
|
|
2393
2390
|
const ctx = reporter.context;
|
|
2394
|
-
return retryWithBackoff(() => invokeWithTimeout(
|
|
2391
|
+
return retryWithBackoff(() => invokeWithTimeout(agent, run, reporter, logger), {
|
|
2395
2392
|
retries,
|
|
2396
2393
|
delayMs: (attempt) => resolveDelay(retryDelayMs, attempt, DEFAULT_RETRY_DELAY_MS),
|
|
2397
2394
|
shouldRetry: () => !reporter.isDead,
|
|
@@ -2405,9 +2402,9 @@ function invokeWithRetry(task, run, reporter, logger) {
|
|
|
2405
2402
|
}
|
|
2406
2403
|
var TIMEOUT_SENTINEL = /* @__PURE__ */ Symbol("task-timeout");
|
|
2407
2404
|
var CANCEL_SENTINEL = /* @__PURE__ */ Symbol("task-canceled");
|
|
2408
|
-
async function invokeWithTimeout(
|
|
2409
|
-
const { timeoutMs, cancelGraceMs } =
|
|
2410
|
-
const handler =
|
|
2405
|
+
async function invokeWithTimeout(agent, run, reporter, logger) {
|
|
2406
|
+
const { timeoutMs, cancelGraceMs } = agent.options;
|
|
2407
|
+
const handler = agent.handler;
|
|
2411
2408
|
const invocation = Promise.resolve().then(() => handler(run.params, reporter.context));
|
|
2412
2409
|
const races = [];
|
|
2413
2410
|
if (timeoutMs && timeoutMs > 0) {
|
|
@@ -2464,7 +2461,7 @@ var TaskAgent = class {
|
|
|
2464
2461
|
this.logger = options.logger;
|
|
2465
2462
|
this.api = options.api;
|
|
2466
2463
|
this.client = options.client;
|
|
2467
|
-
this.
|
|
2464
|
+
this.handler = options.handler;
|
|
2468
2465
|
this.tracker = options.tracker;
|
|
2469
2466
|
this.pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
|
|
2470
2467
|
this.backoff = new Backoff(this.logger, "poll", this.pollIntervalMs);
|
|
@@ -2474,7 +2471,7 @@ var TaskAgent = class {
|
|
|
2474
2471
|
pollIntervalMs;
|
|
2475
2472
|
api;
|
|
2476
2473
|
client;
|
|
2477
|
-
|
|
2474
|
+
handler;
|
|
2478
2475
|
tracker;
|
|
2479
2476
|
backoff;
|
|
2480
2477
|
state = "idle";
|
|
@@ -2490,10 +2487,10 @@ var TaskAgent = class {
|
|
|
2490
2487
|
throw new Error("[hobots] start do agente durante o stop \u2014 aguarde o stop terminar");
|
|
2491
2488
|
}
|
|
2492
2489
|
this.state = "started";
|
|
2493
|
-
this.logger.debug(`polling iniciado (
|
|
2490
|
+
this.logger.debug(`polling iniciado (concorr\xEAncia ${this.handler.options.concurrency})`);
|
|
2494
2491
|
this.schedule(0);
|
|
2495
2492
|
}
|
|
2496
|
-
/**
|
|
2493
|
+
/** Antecipa o próximo poll para já. */
|
|
2497
2494
|
wake() {
|
|
2498
2495
|
if (this.state !== "started") return;
|
|
2499
2496
|
if (this.timer) {
|
|
@@ -2524,21 +2521,14 @@ var TaskAgent = class {
|
|
|
2524
2521
|
void this.tick();
|
|
2525
2522
|
}, delayMs);
|
|
2526
2523
|
}
|
|
2527
|
-
/** Slots livres
|
|
2524
|
+
/** Slots livres do agente — vira o `max` do poll (nunca reivindica trabalho para deixar parado). */
|
|
2528
2525
|
freeSlots() {
|
|
2529
|
-
const
|
|
2530
|
-
|
|
2531
|
-
for (const task of this.registry.remoteTasks()) {
|
|
2532
|
-
const free = task.options.concurrency - this.tracker.activeFor(task.slug);
|
|
2533
|
-
if (free <= 0) continue;
|
|
2534
|
-
tasks.push(task.slug);
|
|
2535
|
-
max += free;
|
|
2536
|
-
}
|
|
2537
|
-
return { tasks, max: Math.min(MAX_CLAIM_PER_POLL, max) };
|
|
2526
|
+
const free = this.handler.options.concurrency - this.tracker.activeCount;
|
|
2527
|
+
return Math.min(MAX_CLAIM_PER_POLL, Math.max(0, free));
|
|
2538
2528
|
}
|
|
2539
2529
|
async tick() {
|
|
2540
2530
|
if (this.state !== "started") return;
|
|
2541
|
-
const
|
|
2531
|
+
const max = this.freeSlots();
|
|
2542
2532
|
if (max === 0) {
|
|
2543
2533
|
this.schedule(this.pollIntervalMs);
|
|
2544
2534
|
return;
|
|
@@ -2548,11 +2538,9 @@ var TaskAgent = class {
|
|
|
2548
2538
|
const response = await this.api.poll(
|
|
2549
2539
|
{
|
|
2550
2540
|
// identidade canônica do agente no painel — o mesmo instanceId do heartbeat,
|
|
2551
|
-
// então os dois canais convergem em um agente
|
|
2552
|
-
// tasks (register + start); o filtro `tasks` abaixo só as que este poll pode rodar.
|
|
2541
|
+
// então os dois canais convergem em um agente
|
|
2553
2542
|
instance: this.options.instanceInfo(),
|
|
2554
|
-
max
|
|
2555
|
-
tasks
|
|
2543
|
+
max
|
|
2556
2544
|
},
|
|
2557
2545
|
this.pollAbort.signal
|
|
2558
2546
|
);
|
|
@@ -2576,17 +2564,7 @@ var TaskAgent = class {
|
|
|
2576
2564
|
this.activeRuns.add(promise);
|
|
2577
2565
|
}
|
|
2578
2566
|
async executeClaimed(run) {
|
|
2579
|
-
|
|
2580
|
-
if (!task || task.mode !== "remote") {
|
|
2581
|
-
const tx = new Transaction(this.client, `run:${run.task}`, "hobots.task", run.id);
|
|
2582
|
-
const reporter = new RunReporter(this.api, run.id, run.task, this.logger, tx);
|
|
2583
|
-
reporter.reportFailure(toRunError(Object.assign(new Error(`tarefa n\xE3o registrada no agente: ${run.task}`), { name: "HandlerNotFound" })));
|
|
2584
|
-
await reporter.finalize();
|
|
2585
|
-
tx.setStatus("error");
|
|
2586
|
-
tx.finish();
|
|
2587
|
-
return;
|
|
2588
|
-
}
|
|
2589
|
-
await executeRun({ run, task, api: this.api, client: this.client, logger: this.logger, tracker: this.tracker });
|
|
2567
|
+
await executeRun({ run, handler: this.handler, api: this.api, client: this.client, logger: this.logger, tracker: this.tracker });
|
|
2590
2568
|
}
|
|
2591
2569
|
};
|
|
2592
2570
|
|
|
@@ -2594,11 +2572,11 @@ var TaskAgent = class {
|
|
|
2594
2572
|
var KEY_RE = /^[a-zA-Z0-9_.-]{1,100}$/;
|
|
2595
2573
|
var MAX_COLUMNS = WIRE_LIMITS.MAX_ITEM_COLUMNS;
|
|
2596
2574
|
var MAX_LABEL_CHARS = WIRE_LIMITS.MAX_ITEM_COLUMN_LABEL_CHARS;
|
|
2597
|
-
function normalizeItemColumns(
|
|
2575
|
+
function normalizeItemColumns(input, logger) {
|
|
2598
2576
|
if (input === void 0 || input === null) return void 0;
|
|
2599
2577
|
const columns = input.columns;
|
|
2600
2578
|
if (typeof input !== "object" || !Array.isArray(columns)) {
|
|
2601
|
-
logger.warn(`
|
|
2579
|
+
logger.warn(`items.columns precisa ser uma lista \u2014 declara\xE7\xE3o de colunas ignorada`);
|
|
2602
2580
|
return void 0;
|
|
2603
2581
|
}
|
|
2604
2582
|
const result = [];
|
|
@@ -2608,15 +2586,15 @@ function normalizeItemColumns(slug, input, logger) {
|
|
|
2608
2586
|
const rawLabel = typeof entry === "object" && entry !== null ? entry.label : void 0;
|
|
2609
2587
|
const key = typeof rawKey === "string" ? rawKey.trim() : "";
|
|
2610
2588
|
if (!KEY_RE.test(key)) {
|
|
2611
|
-
logger.warn(`
|
|
2589
|
+
logger.warn(`coluna de item inv\xE1lida ignorada (${JSON.stringify(rawKey)}) \u2014 use letras, n\xFAmeros, ponto, h\xEDfen e underline`);
|
|
2612
2590
|
continue;
|
|
2613
2591
|
}
|
|
2614
2592
|
if (seen.has(key)) {
|
|
2615
|
-
logger.warn(`
|
|
2593
|
+
logger.warn(`coluna de item "${key}" repetida ignorada`);
|
|
2616
2594
|
continue;
|
|
2617
2595
|
}
|
|
2618
2596
|
if (result.length >= MAX_COLUMNS) {
|
|
2619
|
-
logger.warn(`
|
|
2597
|
+
logger.warn(`no m\xE1ximo ${MAX_COLUMNS} colunas de item \u2014 "${key}" em diante ignoradas`);
|
|
2620
2598
|
break;
|
|
2621
2599
|
}
|
|
2622
2600
|
seen.add(key);
|
|
@@ -2628,15 +2606,15 @@ function normalizeItemColumns(slug, input, logger) {
|
|
|
2628
2606
|
|
|
2629
2607
|
// src/tasks/local.ts
|
|
2630
2608
|
var LocalRunner = class {
|
|
2631
|
-
constructor(
|
|
2632
|
-
this.
|
|
2609
|
+
constructor(handler, deps) {
|
|
2610
|
+
this.handler = handler;
|
|
2633
2611
|
this.deps = deps;
|
|
2634
|
-
this.backoff = new Backoff(deps.logger,
|
|
2612
|
+
this.backoff = new Backoff(deps.logger, "cria\xE7\xE3o da run");
|
|
2635
2613
|
this.done = new Promise((resolve2) => {
|
|
2636
2614
|
this.resolveDone = resolve2;
|
|
2637
2615
|
});
|
|
2638
2616
|
}
|
|
2639
|
-
|
|
2617
|
+
handler;
|
|
2640
2618
|
deps;
|
|
2641
2619
|
abort = new AbortController();
|
|
2642
2620
|
backoff;
|
|
@@ -2647,17 +2625,17 @@ var LocalRunner = class {
|
|
|
2647
2625
|
try {
|
|
2648
2626
|
const run = await this.createRun();
|
|
2649
2627
|
if (!run) return;
|
|
2650
|
-
this.deps.logger.debug(`run ${run.id} aberta
|
|
2628
|
+
this.deps.logger.debug(`run ${run.id} aberta (modo start)`);
|
|
2651
2629
|
await executeRun({
|
|
2652
2630
|
run,
|
|
2653
|
-
|
|
2631
|
+
handler: this.handler,
|
|
2654
2632
|
api: this.deps.api,
|
|
2655
2633
|
client: this.deps.client,
|
|
2656
2634
|
logger: this.deps.logger,
|
|
2657
2635
|
tracker: this.deps.tracker
|
|
2658
2636
|
});
|
|
2659
2637
|
} catch (error) {
|
|
2660
|
-
this.deps.logger.error(
|
|
2638
|
+
this.deps.logger.error("erro inesperado na execu\xE7\xE3o do agente:", error);
|
|
2661
2639
|
} finally {
|
|
2662
2640
|
this.resolveDone();
|
|
2663
2641
|
}
|
|
@@ -2670,7 +2648,7 @@ var LocalRunner = class {
|
|
|
2670
2648
|
for (; ; ) {
|
|
2671
2649
|
if (this.abort.signal.aborted) return void 0;
|
|
2672
2650
|
try {
|
|
2673
|
-
return await this.deps.api.createRun({
|
|
2651
|
+
return await this.deps.api.createRun({ instance: this.deps.instanceInfo() }, this.abort.signal);
|
|
2674
2652
|
} catch (error) {
|
|
2675
2653
|
if (this.abort.signal.aborted) return void 0;
|
|
2676
2654
|
await abortableSleep(this.backoff.next(error), this.abort.signal);
|
|
@@ -2680,45 +2658,6 @@ var LocalRunner = class {
|
|
|
2680
2658
|
};
|
|
2681
2659
|
|
|
2682
2660
|
// src/tasks/registry.ts
|
|
2683
|
-
var TaskRegistry = class {
|
|
2684
|
-
tasks = /* @__PURE__ */ new Map();
|
|
2685
|
-
add(task) {
|
|
2686
|
-
if (this.tasks.has(task.slug)) {
|
|
2687
|
-
throw new Error(`[hobots] task "${task.slug}" j\xE1 registrada \u2014 cada slug s\xF3 pode ter um handler (register ou start)`);
|
|
2688
|
-
}
|
|
2689
|
-
this.tasks.set(task.slug, task);
|
|
2690
|
-
}
|
|
2691
|
-
get(slug) {
|
|
2692
|
-
return this.tasks.get(slug);
|
|
2693
|
-
}
|
|
2694
|
-
get size() {
|
|
2695
|
-
return this.tasks.size;
|
|
2696
|
-
}
|
|
2697
|
-
/** Tasks que chegam pelo poll (register). */
|
|
2698
|
-
remoteTasks() {
|
|
2699
|
-
return [...this.tasks.values()].filter((task) => task.mode === "remote");
|
|
2700
|
-
}
|
|
2701
|
-
/** Tasks abertas pelo próprio bot (start). */
|
|
2702
|
-
localTasks() {
|
|
2703
|
-
return [...this.tasks.values()].filter((task) => task.mode === "local");
|
|
2704
|
-
}
|
|
2705
|
-
/** Todos os slugs, para a presença da instância (`instance.tasks`). */
|
|
2706
|
-
allSlugs() {
|
|
2707
|
-
return [...this.tasks.keys()];
|
|
2708
|
-
}
|
|
2709
|
-
/** Colunas de itens declaradas no código, por slug (`instance.task_items`); undefined se nenhuma task declarou. */
|
|
2710
|
-
itemDeclarations() {
|
|
2711
|
-
let result;
|
|
2712
|
-
for (const task of this.tasks.values()) {
|
|
2713
|
-
if (!task.options.itemColumns) continue;
|
|
2714
|
-
(result ??= {})[task.slug] = { attributes: task.options.itemColumns };
|
|
2715
|
-
}
|
|
2716
|
-
return result;
|
|
2717
|
-
}
|
|
2718
|
-
clear() {
|
|
2719
|
-
this.tasks.clear();
|
|
2720
|
-
}
|
|
2721
|
-
};
|
|
2722
2661
|
var RunTracker = class {
|
|
2723
2662
|
constructor(api) {
|
|
2724
2663
|
this.api = api;
|
|
@@ -2739,12 +2678,6 @@ var RunTracker = class {
|
|
|
2739
2678
|
get activeCount() {
|
|
2740
2679
|
return this.reporters.size;
|
|
2741
2680
|
}
|
|
2742
|
-
/** Runs ativas de uma task (concorrência por task no poll). */
|
|
2743
|
-
activeFor(slug) {
|
|
2744
|
-
let count = 0;
|
|
2745
|
-
for (const reporter of this.reporters) if (reporter.context.task === slug) count++;
|
|
2746
|
-
return count;
|
|
2747
|
-
}
|
|
2748
2681
|
/** A única run ativa, quando há exatamente uma — o que `hobots.*` usa fora de um contexto async. */
|
|
2749
2682
|
singleActive() {
|
|
2750
2683
|
if (this.reporters.size !== 1) return void 0;
|
|
@@ -2833,40 +2766,13 @@ var DEFAULT_RETRIES2 = 1;
|
|
|
2833
2766
|
var DEFAULT_RETRY_DELAY_MS2 = 5e3;
|
|
2834
2767
|
var DEFAULT_CANCEL_GRACE_MS = 1e4;
|
|
2835
2768
|
var MAX_CONCURRENCY = 5;
|
|
2836
|
-
var MAX_SLUG_CHARS = 255;
|
|
2837
|
-
var PROCESS_LEVEL_KEYS = [
|
|
2838
|
-
"clientSecret",
|
|
2839
|
-
"instanceId",
|
|
2840
|
-
"environment",
|
|
2841
|
-
"release",
|
|
2842
|
-
"tags",
|
|
2843
|
-
"debug",
|
|
2844
|
-
"machineMetrics",
|
|
2845
|
-
"captureUnhandled",
|
|
2846
|
-
"installExitHandlers"
|
|
2847
|
-
];
|
|
2848
2769
|
var runtime;
|
|
2849
2770
|
var warnedNoRuntime = false;
|
|
2850
2771
|
function hostname() {
|
|
2851
2772
|
if (!isNode()) return void 0;
|
|
2852
2773
|
return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
|
|
2853
2774
|
}
|
|
2854
|
-
function
|
|
2855
|
-
if (a === b) return true;
|
|
2856
|
-
if (a && b && typeof a === "object" && typeof b === "object") return JSON.stringify(a) === JSON.stringify(b);
|
|
2857
|
-
return false;
|
|
2858
|
-
}
|
|
2859
|
-
function ensureRuntime(options) {
|
|
2860
|
-
if (runtime) {
|
|
2861
|
-
for (const key of PROCESS_LEVEL_KEYS) {
|
|
2862
|
-
if (options[key] !== void 0 && !sameValue(options[key], runtime.options[key])) {
|
|
2863
|
-
runtime.logger.warn(
|
|
2864
|
-
`op\xE7\xE3o "${key}" divergente ignorada \u2014 o primeiro register()/start() define as op\xE7\xF5es do processo`
|
|
2865
|
-
);
|
|
2866
|
-
}
|
|
2867
|
-
}
|
|
2868
|
-
return runtime;
|
|
2869
|
-
}
|
|
2775
|
+
function createRuntime(options, handler) {
|
|
2870
2776
|
const config = resolveConfig({ clientSecret: options.clientSecret });
|
|
2871
2777
|
const logger = makeLogger(options.debug ?? false);
|
|
2872
2778
|
const environment = resolveEnvironmentOption(options.environment);
|
|
@@ -2874,19 +2780,19 @@ function ensureRuntime(options) {
|
|
|
2874
2780
|
{ environment, release: options.release, tags: options.tags, debug: options.debug },
|
|
2875
2781
|
config
|
|
2876
2782
|
);
|
|
2877
|
-
const api = new TasksApi({ baseUrl: config.
|
|
2878
|
-
|
|
2783
|
+
const api = new TasksApi({ baseUrl: config.runsBaseUrl, key: config.key, logger });
|
|
2784
|
+
const rt = {
|
|
2879
2785
|
options: { ...options, clientSecret: void 0 },
|
|
2880
2786
|
config,
|
|
2881
2787
|
client,
|
|
2882
2788
|
logger,
|
|
2883
2789
|
api,
|
|
2884
|
-
|
|
2790
|
+
handler,
|
|
2885
2791
|
tracker: new RunTracker(api),
|
|
2886
2792
|
sampler: void 0,
|
|
2887
2793
|
heartbeat: void 0,
|
|
2888
2794
|
agent: void 0,
|
|
2889
|
-
|
|
2795
|
+
local: void 0,
|
|
2890
2796
|
instanceId: resolveInstanceId(options.instanceId),
|
|
2891
2797
|
environment,
|
|
2892
2798
|
pollIntervalMs: void 0,
|
|
@@ -2896,24 +2802,19 @@ function ensureRuntime(options) {
|
|
|
2896
2802
|
uninstallGlobalHandlers: options.captureUnhandled !== false ? installGlobalHandlers(client) : void 0,
|
|
2897
2803
|
uninstallShutdown: void 0
|
|
2898
2804
|
};
|
|
2899
|
-
runtime.options.clientSecret = options.clientSecret;
|
|
2900
2805
|
logger.debug("inicializado", { endpoint: config.ingestEndpoint });
|
|
2901
|
-
return
|
|
2806
|
+
return rt;
|
|
2902
2807
|
}
|
|
2903
|
-
function
|
|
2904
|
-
if (typeof slug !== "string" || slug.trim().length === 0 || slug.length > MAX_SLUG_CHARS) {
|
|
2905
|
-
throw new Error(`[hobots] ${fn}(): o slug da task precisa ser uma string de 1 a ${MAX_SLUG_CHARS} caracteres`);
|
|
2906
|
-
}
|
|
2808
|
+
function validateHandler(handler, fn) {
|
|
2907
2809
|
if (typeof handler !== "function") {
|
|
2908
|
-
throw new Error(`[hobots] ${fn}(
|
|
2810
|
+
throw new Error(`[hobots] ${fn}(): o handler precisa ser uma fun\xE7\xE3o \u2014 ${fn}(async (params) => { ... })`);
|
|
2909
2811
|
}
|
|
2910
|
-
return slug.trim();
|
|
2911
2812
|
}
|
|
2912
2813
|
function clampConcurrency(value) {
|
|
2913
2814
|
if (value === void 0 || !Number.isFinite(value)) return 1;
|
|
2914
2815
|
return Math.min(MAX_CONCURRENCY, Math.max(1, Math.floor(value)));
|
|
2915
2816
|
}
|
|
2916
|
-
function
|
|
2817
|
+
function resolveRunOptions(options, mode, logger) {
|
|
2917
2818
|
const retries = options.retries === void 0 || !Number.isFinite(options.retries) ? DEFAULT_RETRIES2 : Math.max(0, Math.floor(options.retries));
|
|
2918
2819
|
return {
|
|
2919
2820
|
retries,
|
|
@@ -2923,7 +2824,7 @@ function resolveTaskOptions(slug, options, mode, logger) {
|
|
|
2923
2824
|
logFlushIntervalMs: options.logFlushIntervalMs,
|
|
2924
2825
|
cancelCheckIntervalMs: options.cancelCheckIntervalMs,
|
|
2925
2826
|
cancelGraceMs: options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS,
|
|
2926
|
-
itemColumns: normalizeItemColumns(
|
|
2827
|
+
itemColumns: normalizeItemColumns(options.items, logger)
|
|
2927
2828
|
};
|
|
2928
2829
|
}
|
|
2929
2830
|
function instanceInfo(rt) {
|
|
@@ -2933,10 +2834,10 @@ function instanceInfo(rt) {
|
|
|
2933
2834
|
host: hostname(),
|
|
2934
2835
|
// ambiente da instância: junto do heartbeat, é como o servidor sabe onde o robô roda
|
|
2935
2836
|
environment: rt.environment,
|
|
2936
|
-
//
|
|
2937
|
-
|
|
2837
|
+
// como esta instância trabalha — o painel mostra "sob demanda" / "sempre ligado"
|
|
2838
|
+
mode: rt.handler.mode === "remote" ? "on_demand" : "always_on",
|
|
2938
2839
|
// colunas da aba "itens" declaradas no código — a plataforma usa enquanto o usuário não personalizar
|
|
2939
|
-
|
|
2840
|
+
items: rt.handler.options.itemColumns ? { attributes: rt.handler.options.itemColumns } : void 0,
|
|
2940
2841
|
// o servidor calcula a tolerância de offline (3×) a partir disto em agentes que fazem
|
|
2941
2842
|
// poll; clampado à faixa aceita pelo schema (1s..15min)
|
|
2942
2843
|
poll_interval_ms: rt.agent ? Math.min(Math.max(rt.agent.pollIntervalMs, 1e3), 9e5) : void 0,
|
|
@@ -2949,22 +2850,22 @@ function ensureAgent(rt) {
|
|
|
2949
2850
|
api: rt.api,
|
|
2950
2851
|
client: rt.client,
|
|
2951
2852
|
logger: rt.logger,
|
|
2952
|
-
|
|
2853
|
+
handler: rt.handler,
|
|
2953
2854
|
tracker: rt.tracker,
|
|
2954
2855
|
instanceInfo: () => instanceInfo(rt),
|
|
2955
2856
|
pollIntervalMs: rt.pollIntervalMs
|
|
2956
2857
|
});
|
|
2957
2858
|
return rt.agent;
|
|
2958
2859
|
}
|
|
2959
|
-
function launchLocal(rt
|
|
2960
|
-
const runner = new LocalRunner(
|
|
2860
|
+
function launchLocal(rt) {
|
|
2861
|
+
const runner = new LocalRunner(rt.handler, {
|
|
2961
2862
|
api: rt.api,
|
|
2962
2863
|
client: rt.client,
|
|
2963
2864
|
logger: rt.logger,
|
|
2964
2865
|
tracker: rt.tracker,
|
|
2965
2866
|
instanceInfo: () => instanceInfo(rt)
|
|
2966
2867
|
});
|
|
2967
|
-
rt.
|
|
2868
|
+
rt.local = runner;
|
|
2968
2869
|
void runner.run();
|
|
2969
2870
|
}
|
|
2970
2871
|
async function boot(rt) {
|
|
@@ -2986,8 +2887,8 @@ async function boot(rt) {
|
|
|
2986
2887
|
if (rt.options.installExitHandlers !== false) {
|
|
2987
2888
|
rt.uninstallShutdown = installShutdownHandlers(rt.tracker);
|
|
2988
2889
|
}
|
|
2989
|
-
if (rt.
|
|
2990
|
-
|
|
2890
|
+
if (rt.handler.mode === "remote") ensureAgent(rt).start();
|
|
2891
|
+
else launchLocal(rt);
|
|
2991
2892
|
}
|
|
2992
2893
|
function scheduleBoot(rt) {
|
|
2993
2894
|
if (rt.bootScheduled) return;
|
|
@@ -2996,34 +2897,23 @@ function scheduleBoot(rt) {
|
|
|
2996
2897
|
rt.bootPromise = boot(rt).catch((error) => rt.logger.error("falha ao iniciar o SDK:", error));
|
|
2997
2898
|
});
|
|
2998
2899
|
}
|
|
2999
|
-
function
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
if (rt.pollIntervalMs === void 0 && !rt.agent) rt.pollIntervalMs = options.pollIntervalMs;
|
|
3004
|
-
else if (rt.pollIntervalMs !== options.pollIntervalMs) {
|
|
3005
|
-
rt.logger.warn('op\xE7\xE3o "pollIntervalMs" divergente ignorada \u2014 o primeiro register() define o intervalo do poll');
|
|
3006
|
-
}
|
|
3007
|
-
}
|
|
3008
|
-
const registered = { slug, mode, handler, options: resolveTaskOptions(slug, options, mode, rt.logger) };
|
|
3009
|
-
rt.registry.add(registered);
|
|
3010
|
-
if (!rt.booted) {
|
|
3011
|
-
scheduleBoot(rt);
|
|
3012
|
-
return;
|
|
3013
|
-
}
|
|
3014
|
-
if (mode === "remote") {
|
|
3015
|
-
const agent = ensureAgent(rt);
|
|
3016
|
-
agent.start();
|
|
3017
|
-
agent.wake();
|
|
3018
|
-
} else {
|
|
3019
|
-
launchLocal(rt, registered);
|
|
2900
|
+
function startAgent(fn, mode, handler, options) {
|
|
2901
|
+
validateHandler(handler, fn);
|
|
2902
|
+
if (runtime) {
|
|
2903
|
+
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)`);
|
|
3020
2904
|
}
|
|
2905
|
+
const logger = makeLogger(options.debug ?? false);
|
|
2906
|
+
const agentHandler = { mode, handler, options: resolveRunOptions(options, mode, logger) };
|
|
2907
|
+
const rt = createRuntime(options, agentHandler);
|
|
2908
|
+
if (mode === "remote" && options.pollIntervalMs !== void 0) rt.pollIntervalMs = options.pollIntervalMs;
|
|
2909
|
+
runtime = rt;
|
|
2910
|
+
scheduleBoot(rt);
|
|
3021
2911
|
}
|
|
3022
|
-
function register(
|
|
3023
|
-
|
|
2912
|
+
function register(handler, options = {}) {
|
|
2913
|
+
startAgent("register", "remote", handler, options);
|
|
3024
2914
|
}
|
|
3025
|
-
function start(
|
|
3026
|
-
|
|
2915
|
+
function start(handler, options = {}) {
|
|
2916
|
+
startAgent("start", "local", handler, options);
|
|
3027
2917
|
}
|
|
3028
2918
|
function currentReporter() {
|
|
3029
2919
|
return currentRunReporter() ?? runtime?.tracker.singleActive();
|
|
@@ -3037,7 +2927,7 @@ function toRunLogLevel(type) {
|
|
|
3037
2927
|
function forwardAttachment(rt, attachment, message, type) {
|
|
3038
2928
|
const reporter = currentReporter();
|
|
3039
2929
|
if (!reporter) {
|
|
3040
|
-
rt.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o
|
|
2930
|
+
rt.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o");
|
|
3041
2931
|
return;
|
|
3042
2932
|
}
|
|
3043
2933
|
const withMeta = typeof attachment === "object" && !isImageAttachment(attachment) && "image" in attachment ? image(attachment.image, { caption: attachment.caption, contentType: attachment.contentType }) : attachment;
|
|
@@ -3048,7 +2938,7 @@ function captureException(error, hint) {
|
|
|
3048
2938
|
if (!rt) {
|
|
3049
2939
|
if (!warnedNoRuntime) {
|
|
3050
2940
|
warnedNoRuntime = true;
|
|
3051
|
-
console.warn("[hobots] captureException antes de
|
|
2941
|
+
console.warn("[hobots] captureException antes de iniciar o agente (register/start) \u2014 evento descartado");
|
|
3052
2942
|
}
|
|
3053
2943
|
return void 0;
|
|
3054
2944
|
}
|
|
@@ -3064,7 +2954,7 @@ async function close(timeoutMs = 3e4) {
|
|
|
3064
2954
|
runtime = void 0;
|
|
3065
2955
|
await rt.bootPromise;
|
|
3066
2956
|
if (rt.agent) await rt.agent.stop(0);
|
|
3067
|
-
|
|
2957
|
+
rt.local?.stop();
|
|
3068
2958
|
const idle = await rt.tracker.waitIdle(timeoutMs);
|
|
3069
2959
|
if (!idle) {
|
|
3070
2960
|
rt.logger.warn(`${rt.tracker.activeCount} run(s) ainda em execu\xE7\xE3o ap\xF3s ${timeoutMs}ms \u2014 abandonando a espera`);
|
|
@@ -3082,15 +2972,14 @@ var warned = /* @__PURE__ */ new Set();
|
|
|
3082
2972
|
function warnOutsideRun(member) {
|
|
3083
2973
|
if (warned.has(member)) return;
|
|
3084
2974
|
warned.add(member);
|
|
3085
|
-
const message = `hobots.${member} usado fora de uma execu\xE7\xE3o
|
|
2975
|
+
const message = `hobots.${member} usado fora de uma execu\xE7\xE3o \u2014 ignorado (inicie o agente com register()/start() e use dentro do handler)`;
|
|
3086
2976
|
const logger = activeLogger();
|
|
3087
2977
|
if (logger) logger.warn(message);
|
|
3088
2978
|
else console.warn(`[hobots] ${message}`);
|
|
3089
2979
|
}
|
|
3090
|
-
var FALLBACK_TASK = "hobots";
|
|
3091
2980
|
var fallbackLogAt = (level) => (message, ...rest) => {
|
|
3092
2981
|
const { text, image: dropped } = splitLogArgs(message, rest);
|
|
3093
|
-
printRunLog(
|
|
2982
|
+
printRunLog(level, dropped ? `${text} [imagem descartada]` : text);
|
|
3094
2983
|
};
|
|
3095
2984
|
var fallbackLog = {
|
|
3096
2985
|
debug: fallbackLogAt("debug"),
|
|
@@ -3107,7 +2996,7 @@ var fallbackEachSink = {
|
|
|
3107
2996
|
advance: noop,
|
|
3108
2997
|
checkCanceled: noop,
|
|
3109
2998
|
isAborted: () => false,
|
|
3110
|
-
logError: (message) => printRunLog(
|
|
2999
|
+
logError: (message) => printRunLog("error", message)
|
|
3111
3000
|
};
|
|
3112
3001
|
var fallbackItems = {
|
|
3113
3002
|
add: noop,
|
|
@@ -3134,7 +3023,6 @@ function method(value) {
|
|
|
3134
3023
|
var hobots = Object.defineProperties({}, {
|
|
3135
3024
|
// ── contexto da run atual (getters) ─────────────────────────────────────────
|
|
3136
3025
|
runId: getter("runId", () => ""),
|
|
3137
|
-
task: getter("task", () => ""),
|
|
3138
3026
|
params: getter("params", () => ({})),
|
|
3139
3027
|
log: getter("log", () => fallbackLog),
|
|
3140
3028
|
items: getter("items", () => fallbackItems),
|
|
@@ -3148,12 +3036,12 @@ var hobots = Object.defineProperties({}, {
|
|
|
3148
3036
|
return reporter ? reporter.context.retry(fn, options) : retryWithBackoff(fn, options);
|
|
3149
3037
|
}),
|
|
3150
3038
|
// ── processo ────────────────────────────────────────────────────────────────
|
|
3151
|
-
register: method((
|
|
3152
|
-
register(
|
|
3039
|
+
register: method((handler, options) => {
|
|
3040
|
+
register(handler, options);
|
|
3153
3041
|
installGlobalHobots();
|
|
3154
3042
|
}),
|
|
3155
|
-
start: method((
|
|
3156
|
-
start(
|
|
3043
|
+
start: method((handler, options) => {
|
|
3044
|
+
start(handler, options);
|
|
3157
3045
|
installGlobalHobots();
|
|
3158
3046
|
}),
|
|
3159
3047
|
close: method((timeoutMs) => close(timeoutMs)),
|