hobots 3.4.0 → 4.1.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/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 ? "3.4.0" : "0.0.0-dev";
99
+ var SDK_VERSION = true ? "4.1.0" : "0.0.0-dev";
100
100
  function isNode() {
101
101
  return typeof process !== "undefined" && typeof process.versions?.node === "string";
102
102
  }
@@ -281,7 +281,6 @@ var protocol_limits_default = {
281
281
  MAX_LOGS_PER_PATCH: 100,
282
282
  MAX_ERROR_MESSAGE_CHARS: 1e4,
283
283
  MAX_ERROR_STACK_CHARS: 2e4,
284
- MAX_ITEMS_PER_RUN: 1e4,
285
284
  MAX_ITEMS_PER_REQUEST: 500,
286
285
  MAX_ITEM_MESSAGE_CHARS: 2e3,
287
286
  MAX_ITEM_PAYLOAD_BYTES: 16384,
@@ -580,7 +579,7 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
580
579
  const origin = fromOption ? "clientSecret" : `env ${CLIENT_SECRET_ENV}`;
581
580
  if (!key) {
582
581
  throw new Error(
583
- `${prefix} Client Secret ausente \u2014 defina a env ${CLIENT_SECRET_ENV} (ou passe clientSecret nas op\xE7\xF5es da task); copie o Client Secret do agente em Configura\xE7\xF5es \u2192 Agentes`
582
+ `${prefix} Client Secret ausente \u2014 defina a env ${CLIENT_SECRET_ENV} (ou passe clientSecret nas op\xE7\xF5es do register/start); copie o Client Secret do agente em Configura\xE7\xF5es \u2192 Agentes`
584
583
  );
585
584
  }
586
585
  if (!SECRET_PATTERN.test(key)) {
@@ -593,7 +592,7 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
593
592
  key,
594
593
  baseUrl,
595
594
  ingestEndpoint: `${baseUrl}/v1/ingest`,
596
- tasksBaseUrl: `${baseUrl}/v1/tasks`
595
+ runsBaseUrl: `${baseUrl}/v1/runs`
597
596
  };
598
597
  }
599
598
 
@@ -957,7 +956,7 @@ var TasksApi = class {
957
956
  const { signal: timeoutSignal, cleanup } = withTimeoutSignal(UPDATE_TIMEOUT_MS, signal);
958
957
  let response;
959
958
  try {
960
- response = await fetch(`${this.baseUrl}/runs`, {
959
+ response = await fetch(`${this.baseUrl}`, {
961
960
  redirect: "error",
962
961
  method: "POST",
963
962
  headers: this.headers(),
@@ -971,12 +970,11 @@ var TasksApi = class {
971
970
  throw new TasksApiError(`cria\xE7\xE3o da run falhou (HTTP ${response.status})`, response.status);
972
971
  }
973
972
  const body = await response.json().catch(() => null);
974
- if (typeof body?.id !== "string" || typeof body.task !== "string") {
975
- throw new TasksApiError("resposta inv\xE1lida da cria\xE7\xE3o de run (id/task ausentes)", 0);
973
+ if (typeof body?.id !== "string") {
974
+ throw new TasksApiError("resposta inv\xE1lida da cria\xE7\xE3o de run (id ausente)", 0);
976
975
  }
977
976
  return {
978
977
  id: body.id,
979
- task: body.task,
980
978
  params: body.params && typeof body.params === "object" && !Array.isArray(body.params) ? body.params : {},
981
979
  form: null,
982
980
  source: typeof body.source === "string" ? body.source : "agent",
@@ -991,9 +989,9 @@ var TasksApi = class {
991
989
  }
992
990
  const valid = [];
993
991
  for (const run of runs.slice(0, Math.max(0, max))) {
994
- if (typeof run?.id !== "string" || typeof run.task !== "string") continue;
992
+ if (typeof run?.id !== "string") continue;
995
993
  const params = run.params && typeof run.params === "object" && !Array.isArray(run.params) ? run.params : {};
996
- valid.push({ ...run, id: run.id, task: run.task, params });
994
+ valid.push({ ...run, id: run.id, params });
997
995
  }
998
996
  if (valid.length < runs.length) {
999
997
  this.logger.warn(`poll retornou ${runs.length - valid.length} run(s) inv\xE1lida(s)/excedente(s) \u2014 descartadas`);
@@ -1008,7 +1006,7 @@ var TasksApi = class {
1008
1006
  let response;
1009
1007
  try {
1010
1008
  response = await fetchWithRetry(
1011
- () => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
1009
+ () => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}`, {
1012
1010
  redirect: "error",
1013
1011
  method: "PATCH",
1014
1012
  headers: this.headers(),
@@ -1030,7 +1028,7 @@ var TasksApi = class {
1030
1028
  * que a run já foi cancelada/finalizada, o que é aceitável nesse caminho.
1031
1029
  */
1032
1030
  async updateRunOnce(runId, update, timeoutMs = 2e3) {
1033
- await fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
1031
+ await fetch(`${this.baseUrl}/${encodeURIComponent(runId)}`, {
1034
1032
  redirect: "error",
1035
1033
  method: "PATCH",
1036
1034
  headers: this.headers(),
@@ -1047,7 +1045,7 @@ var TasksApi = class {
1047
1045
  let response;
1048
1046
  try {
1049
1047
  response = await fetchWithRetry(
1050
- () => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}/items`, {
1048
+ () => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/items`, {
1051
1049
  redirect: "error",
1052
1050
  method: "POST",
1053
1051
  headers: this.headers(),
@@ -1077,7 +1075,7 @@ var TasksApi = class {
1077
1075
  () => withRequestTimeout(
1078
1076
  UPDATE_TIMEOUT_MS,
1079
1077
  signal,
1080
- (timeoutSignal) => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}/attachments`, {
1078
+ (timeoutSignal) => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/attachments`, {
1081
1079
  redirect: "error",
1082
1080
  method: "POST",
1083
1081
  headers: this.headers(),
@@ -1128,7 +1126,7 @@ var TasksApi = class {
1128
1126
  /** Marca o anexo como enviado (ou falho). Best-effort — a leitura não depende disso. */
1129
1127
  async confirmAttachment(runId, attachmentId, request) {
1130
1128
  const response = await fetch(
1131
- `${this.baseUrl}/runs/${encodeURIComponent(runId)}/attachments/${encodeURIComponent(attachmentId)}`,
1129
+ `${this.baseUrl}/${encodeURIComponent(runId)}/attachments/${encodeURIComponent(attachmentId)}`,
1132
1130
  {
1133
1131
  redirect: "error",
1134
1132
  method: "PATCH",
@@ -1386,8 +1384,8 @@ function splitLogArgs(message, rest) {
1386
1384
  if (text.length === 0) return { text: last.caption !== void 0 ? safeString(last.caption) : "captura de tela", image: last };
1387
1385
  return { text: joinLogArgs(text[0], text.slice(1)), image: last };
1388
1386
  }
1389
- function printRunLog(task, level, message) {
1390
- const line = colorize(`[hobots:${task}] ${level.toUpperCase()}`, LOG_LEVEL_COLOR[level]);
1387
+ function printRunLog(level, message) {
1388
+ const line = colorize(`[hobots] ${level.toUpperCase()}`, LOG_LEVEL_COLOR[level]);
1391
1389
  const method2 = level === "warning" ? "warn" : level === "success" ? "info" : level;
1392
1390
  console[method2](line, message);
1393
1391
  }
@@ -1415,7 +1413,6 @@ var MAX_CANCEL_CHECK_INTERVAL_MS = 15e3;
1415
1413
  var MAX_LOG_MESSAGE_CHARS = WIRE_LIMITS.MAX_LOG_MESSAGE_CHARS;
1416
1414
  var MAX_LOGS_PER_PATCH = WIRE_LIMITS.MAX_LOGS_PER_PATCH;
1417
1415
  var FLUSH_AT_BUFFERED = 20;
1418
- var MAX_ITEMS_PER_RUN = WIRE_LIMITS.MAX_ITEMS_PER_RUN;
1419
1416
  var MAX_ITEMS_PER_REQUEST = WIRE_LIMITS.MAX_ITEMS_PER_REQUEST;
1420
1417
  var MAX_ITEM_MESSAGE_CHARS = WIRE_LIMITS.MAX_ITEM_MESSAGE_CHARS;
1421
1418
  var MAX_ITEM_PAYLOAD_BYTES = WIRE_LIMITS.MAX_ITEM_PAYLOAD_BYTES;
@@ -1433,10 +1430,11 @@ function scrubPayloadValue(value, depth) {
1433
1430
  return out;
1434
1431
  }
1435
1432
  var RunReporter = class {
1436
- constructor(api, runId, task, logger, tx, flushIntervalMs, cancelCheckIntervalMs, params = {}) {
1433
+ constructor(api, runId, logger, tx, flushIntervalMs, cancelCheckIntervalMs, params = {}) {
1437
1434
  this.api = api;
1438
1435
  this.runId = runId;
1439
1436
  this.logger = logger;
1437
+ this.tx = tx;
1440
1438
  this.flushIntervalMs = flushIntervalMs ?? DEFAULT_LOG_FLUSH_INTERVAL_MS;
1441
1439
  this.cancelCheckIntervalMs = Math.min(
1442
1440
  MAX_CANCEL_CHECK_INTERVAL_MS,
@@ -1515,12 +1513,12 @@ var RunReporter = class {
1515
1513
  const sleep2 = (ms) => cancelableSleep(ms, signal);
1516
1514
  this.context = {
1517
1515
  runId,
1518
- task,
1519
1516
  params,
1520
1517
  log,
1521
1518
  items,
1522
1519
  progress,
1523
1520
  transaction: tx,
1521
+ flush: (timeoutMs) => this.flush(timeoutMs),
1524
1522
  signal,
1525
1523
  throwIfCanceled: checkCanceled,
1526
1524
  sleep: sleep2,
@@ -1530,6 +1528,7 @@ var RunReporter = class {
1530
1528
  api;
1531
1529
  runId;
1532
1530
  logger;
1531
+ tx;
1533
1532
  context;
1534
1533
  buffer = [];
1535
1534
  dead = false;
@@ -1547,7 +1546,6 @@ var RunReporter = class {
1547
1546
  itemsChain = Promise.resolve();
1548
1547
  itemsTimer;
1549
1548
  itemSeq = 0;
1550
- itemCount = 0;
1551
1549
  /** Servidor sem o recurso (404) — para de tentar (e de avisar) nesta run. */
1552
1550
  itemsUnsupported = false;
1553
1551
  /** Cap de itens por run atingido (client-side ou 429) — degrada com um aviso só. */
@@ -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(this.context.task, level, attachmentId ? `${scrubbed} [imagem]` : scrubbed);
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). */
@@ -1670,6 +1668,21 @@ var RunReporter = class {
1670
1668
  this.enqueue({ status: "running", logs: logs.slice(i, i + MAX_LOGS_PER_PATCH) });
1671
1669
  }
1672
1670
  }
1671
+ /**
1672
+ * `hobots.flush()`: o bot decide quando entregar o que já fez — steps raiz finalizadas
1673
+ * viram transactions parciais da run, e itens, logs e progresso pendentes saem já, sem
1674
+ * esperar o throttle. Aguarda a entrega com teto. Nunca lança (pode ir num `finally`).
1675
+ */
1676
+ async flush(timeoutMs = ITEMS_DRAIN_MS) {
1677
+ this.tx.flush();
1678
+ this.flushItems();
1679
+ if (this.progressDirty) this.flushProgress();
1680
+ else this.flushLogs();
1681
+ await Promise.race([
1682
+ Promise.all([this.itemsChain, this.chain, this.tx.drain(timeoutMs)]).catch(() => void 0),
1683
+ new Promise((resolve2) => unrefTimer(setTimeout(resolve2, timeoutMs)))
1684
+ ]);
1685
+ }
1673
1686
  /** Primeiro PATCH (claimed → running). Retorna false se a run já morreu (cancelada). */
1674
1687
  async reportRunning() {
1675
1688
  this.enqueue({ status: "running" });
@@ -1780,14 +1793,6 @@ var RunReporter = class {
1780
1793
  */
1781
1794
  addItem(status, message, payload, id, image3) {
1782
1795
  if (this.itemsClosed || this.itemsUnsupported) return;
1783
- if (this.itemCount >= MAX_ITEMS_PER_RUN) {
1784
- if (!this.itemsQuotaExceeded) {
1785
- this.itemsQuotaExceeded = true;
1786
- this.log("warning", `itens descartados: limite de ${MAX_ITEMS_PER_RUN} por execu\xE7\xE3o atingido`);
1787
- }
1788
- return;
1789
- }
1790
- this.itemCount++;
1791
1796
  let scrubbedPayload;
1792
1797
  if (payload) {
1793
1798
  scrubbedPayload = scrubPayloadValue(payload, 0);
@@ -2082,6 +2087,7 @@ function currentRunReporter() {
2082
2087
  }
2083
2088
 
2084
2089
  // src/transaction.ts
2090
+ var FLUSH_TRANSACTION_OP = "hobots.run.flush";
2085
2091
  var MAX_STEP_MESSAGE_CHARS = 2e3;
2086
2092
  function stepMessage(text) {
2087
2093
  return truncate(scrubSecrets(text), MAX_STEP_MESSAGE_CHARS);
@@ -2269,7 +2275,7 @@ var Transaction = class {
2269
2275
  if (this.attachmentSink) return this.attachmentSink(image3, nowISO());
2270
2276
  if (!this.warnedNoAttachmentSink) {
2271
2277
  this.warnedNoAttachmentSink = true;
2272
- console.warn("[hobots] imagem de step descartada \u2014 s\xF3 \xE9 suportada dentro de uma execu\xE7\xE3o de task");
2278
+ console.warn("[hobots] imagem de step descartada \u2014 s\xF3 \xE9 suportada dentro de uma execu\xE7\xE3o");
2273
2279
  }
2274
2280
  return void 0;
2275
2281
  }
@@ -2303,6 +2309,60 @@ var Transaction = class {
2303
2309
  this.name = name;
2304
2310
  return this;
2305
2311
  }
2312
+ /**
2313
+ * Envia já as steps raiz finalizadas (e todas as descendentes, fechando as que
2314
+ * ficaram abertas) como transactions parciais da mesma run (`op` hobots.run.flush,
2315
+ * `event_id` novo, até 200 steps cada) e as tira da memória. Steps raiz ainda abertas
2316
+ * ficam para o próximo flush ou para o `finish()`. É assim que um agente sempre
2317
+ * ligado (`start()`), cuja run não termina, entrega as etapas enquanto processa.
2318
+ * Devolve quantas steps foram enviadas.
2319
+ */
2320
+ flush() {
2321
+ if (this.finished) return 0;
2322
+ const ready = /* @__PURE__ */ new Set();
2323
+ for (const step of this.steps) {
2324
+ if (step.parentStepId === this.stepId && step.endTimestamp) ready.add(step.stepId);
2325
+ }
2326
+ if (ready.size === 0) return 0;
2327
+ const batch = [];
2328
+ const remaining = [];
2329
+ for (const step of this.steps) {
2330
+ if (ready.has(step.stepId) || ready.has(step.parentStepId)) {
2331
+ ready.add(step.stepId);
2332
+ batch.push(step);
2333
+ } else {
2334
+ remaining.push(step);
2335
+ }
2336
+ }
2337
+ for (const step of batch) step.finish();
2338
+ this.steps.splice(0, this.steps.length, ...remaining);
2339
+ if (this.client) {
2340
+ for (let i = 0; i < batch.length; i += MAX_STEPS) {
2341
+ const chunk = batch.slice(i, i + MAX_STEPS);
2342
+ const startTimestamp = chunk.reduce((min, step) => step.startTimestamp < min ? step.startTimestamp : min, chunk[0].startTimestamp);
2343
+ const status = chunk.some((step) => step.status === "error") ? "error" : "ok";
2344
+ this.client.sendTransaction(this.payload(uuid4(), FLUSH_TRANSACTION_OP, status, startTimestamp, nowISO(), chunk));
2345
+ }
2346
+ }
2347
+ return batch.length;
2348
+ }
2349
+ /** Entrega já (com teto) o que está na fila do transport — as transactions parciais inclusive. */
2350
+ async drain(timeoutMs) {
2351
+ await this.client?.flush(timeoutMs);
2352
+ }
2353
+ payload(eventId, op, status, startTimestamp, timestamp, steps) {
2354
+ return {
2355
+ event_id: eventId,
2356
+ name: this.name,
2357
+ op,
2358
+ status,
2359
+ start_timestamp: startTimestamp,
2360
+ timestamp,
2361
+ steps: steps.map((step) => step.toJSON()),
2362
+ tags: Object.keys(this.tags).length > 0 ? { ...this.tags } : void 0,
2363
+ run_id: this.runId
2364
+ };
2365
+ }
2306
2366
  finish() {
2307
2367
  if (this.finished) return;
2308
2368
  this.finished = true;
@@ -2317,31 +2377,27 @@ var Transaction = class {
2317
2377
  this.status
2318
2378
  );
2319
2379
  if (!this.client) return;
2320
- const payload = {
2321
- event_id: this.eventId,
2322
- name: this.name,
2323
- op: this.op,
2324
- status: this.status,
2325
- start_timestamp: this.startTimestamp,
2326
- timestamp: endTimestamp,
2327
- steps: this.steps.map((step) => step.toJSON()),
2328
- tags: Object.keys(this.tags).length > 0 ? this.tags : void 0,
2329
- run_id: this.runId
2330
- };
2331
- this.client.sendTransaction(payload);
2380
+ const steps = this.steps;
2381
+ let first = 0;
2382
+ while (steps.length - first > MAX_STEPS) {
2383
+ const chunk = steps.slice(first, first + MAX_STEPS);
2384
+ const status = chunk.some((step) => step.status === "error") ? "error" : "ok";
2385
+ this.client.sendTransaction(this.payload(uuid4(), FLUSH_TRANSACTION_OP, status, chunk[0].startTimestamp, endTimestamp, chunk));
2386
+ first += MAX_STEPS;
2387
+ }
2388
+ this.client.sendTransaction(this.payload(this.eventId, this.op, this.status, this.startTimestamp, endTimestamp, steps.slice(first)));
2332
2389
  }
2333
2390
  };
2334
2391
 
2335
2392
  // src/tasks/runner.ts
2336
2393
  var DEFAULT_RETRY_DELAY_MS = 5e3;
2337
2394
  async function executeRun(input) {
2338
- const { run, task, api, client, logger, tracker } = input;
2339
- const options = task.options;
2340
- const tx = new Transaction(client, `run:${run.task}`, "hobots.task", run.id);
2395
+ const { run, handler, api, client, logger, tracker } = input;
2396
+ const options = handler.options;
2397
+ const tx = new Transaction(client, "run", "hobots.run", run.id);
2341
2398
  const reporter = new RunReporter(
2342
2399
  api,
2343
2400
  run.id,
2344
- run.task,
2345
2401
  logger,
2346
2402
  tx,
2347
2403
  options.logFlushIntervalMs,
@@ -2357,9 +2413,9 @@ async function executeRun(input) {
2357
2413
  tx.finish();
2358
2414
  return;
2359
2415
  }
2360
- logger.debug(`executando run ${run.id} (${run.task})`);
2416
+ logger.debug(`executando run ${run.id}`);
2361
2417
  try {
2362
- await runWithRunContext({ reporter }, () => invokeWithRetry(task, run, reporter, logger));
2418
+ await runWithRunContext({ reporter }, () => invokeWithRetry(handler, run, reporter, logger));
2363
2419
  await reporter.finalizeAttachments();
2364
2420
  await reporter.finalizeItems();
2365
2421
  warnIncompleteProgress(reporter);
@@ -2387,11 +2443,11 @@ function warnIncompleteProgress(reporter) {
2387
2443
  `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
2444
  );
2389
2445
  }
2390
- function invokeWithRetry(task, run, reporter, logger) {
2391
- const { retries, retryDelayMs } = task.options;
2446
+ function invokeWithRetry(agent, run, reporter, logger) {
2447
+ const { retries, retryDelayMs } = agent.options;
2392
2448
  const total = retries + 1;
2393
2449
  const ctx = reporter.context;
2394
- return retryWithBackoff(() => invokeWithTimeout(task, run, reporter, logger), {
2450
+ return retryWithBackoff(() => invokeWithTimeout(agent, run, reporter, logger), {
2395
2451
  retries,
2396
2452
  delayMs: (attempt) => resolveDelay(retryDelayMs, attempt, DEFAULT_RETRY_DELAY_MS),
2397
2453
  shouldRetry: () => !reporter.isDead,
@@ -2405,9 +2461,9 @@ function invokeWithRetry(task, run, reporter, logger) {
2405
2461
  }
2406
2462
  var TIMEOUT_SENTINEL = /* @__PURE__ */ Symbol("task-timeout");
2407
2463
  var CANCEL_SENTINEL = /* @__PURE__ */ Symbol("task-canceled");
2408
- async function invokeWithTimeout(task, run, reporter, logger) {
2409
- const { timeoutMs, cancelGraceMs } = task.options;
2410
- const handler = task.handler;
2464
+ async function invokeWithTimeout(agent, run, reporter, logger) {
2465
+ const { timeoutMs, cancelGraceMs } = agent.options;
2466
+ const handler = agent.handler;
2411
2467
  const invocation = Promise.resolve().then(() => handler(run.params, reporter.context));
2412
2468
  const races = [];
2413
2469
  if (timeoutMs && timeoutMs > 0) {
@@ -2464,7 +2520,7 @@ var TaskAgent = class {
2464
2520
  this.logger = options.logger;
2465
2521
  this.api = options.api;
2466
2522
  this.client = options.client;
2467
- this.registry = options.registry;
2523
+ this.handler = options.handler;
2468
2524
  this.tracker = options.tracker;
2469
2525
  this.pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
2470
2526
  this.backoff = new Backoff(this.logger, "poll", this.pollIntervalMs);
@@ -2474,7 +2530,7 @@ var TaskAgent = class {
2474
2530
  pollIntervalMs;
2475
2531
  api;
2476
2532
  client;
2477
- registry;
2533
+ handler;
2478
2534
  tracker;
2479
2535
  backoff;
2480
2536
  state = "idle";
@@ -2490,10 +2546,10 @@ var TaskAgent = class {
2490
2546
  throw new Error("[hobots] start do agente durante o stop \u2014 aguarde o stop terminar");
2491
2547
  }
2492
2548
  this.state = "started";
2493
- this.logger.debug(`polling iniciado (tasks: ${this.registry.remoteTasks().map((task) => task.slug).join(", ")})`);
2549
+ this.logger.debug(`polling iniciado (concorr\xEAncia ${this.handler.options.concurrency})`);
2494
2550
  this.schedule(0);
2495
2551
  }
2496
- /** Uma task registrada depois do boot entra no próximo poll — que é antecipado para já. */
2552
+ /** Antecipa o próximo poll para já. */
2497
2553
  wake() {
2498
2554
  if (this.state !== "started") return;
2499
2555
  if (this.timer) {
@@ -2524,21 +2580,14 @@ var TaskAgent = class {
2524
2580
  void this.tick();
2525
2581
  }, delayMs);
2526
2582
  }
2527
- /** Slots livres por task remota — a soma vira o `max` do poll e as tasks com slot viram o filtro. */
2583
+ /** Slots livres do agente — vira o `max` do poll (nunca reivindica trabalho para deixar parado). */
2528
2584
  freeSlots() {
2529
- const tasks = [];
2530
- let max = 0;
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) };
2585
+ const free = this.handler.options.concurrency - this.tracker.activeCount;
2586
+ return Math.min(MAX_CLAIM_PER_POLL, Math.max(0, free));
2538
2587
  }
2539
2588
  async tick() {
2540
2589
  if (this.state !== "started") return;
2541
- const { tasks, max } = this.freeSlots();
2590
+ const max = this.freeSlots();
2542
2591
  if (max === 0) {
2543
2592
  this.schedule(this.pollIntervalMs);
2544
2593
  return;
@@ -2548,11 +2597,9 @@ var TaskAgent = class {
2548
2597
  const response = await this.api.poll(
2549
2598
  {
2550
2599
  // identidade canônica do agente no painel — o mesmo instanceId do heartbeat,
2551
- // então os dois canais convergem em um agente. `instance.tasks` lista TODAS as
2552
- // tasks (register + start); o filtro `tasks` abaixo só as que este poll pode rodar.
2600
+ // então os dois canais convergem em um agente
2553
2601
  instance: this.options.instanceInfo(),
2554
- max,
2555
- tasks
2602
+ max
2556
2603
  },
2557
2604
  this.pollAbort.signal
2558
2605
  );
@@ -2576,17 +2623,7 @@ var TaskAgent = class {
2576
2623
  this.activeRuns.add(promise);
2577
2624
  }
2578
2625
  async executeClaimed(run) {
2579
- const task = this.registry.get(run.task);
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 });
2626
+ await executeRun({ run, handler: this.handler, api: this.api, client: this.client, logger: this.logger, tracker: this.tracker });
2590
2627
  }
2591
2628
  };
2592
2629
 
@@ -2594,11 +2631,11 @@ var TaskAgent = class {
2594
2631
  var KEY_RE = /^[a-zA-Z0-9_.-]{1,100}$/;
2595
2632
  var MAX_COLUMNS = WIRE_LIMITS.MAX_ITEM_COLUMNS;
2596
2633
  var MAX_LABEL_CHARS = WIRE_LIMITS.MAX_ITEM_COLUMN_LABEL_CHARS;
2597
- function normalizeItemColumns(slug, input, logger) {
2634
+ function normalizeItemColumns(input, logger) {
2598
2635
  if (input === void 0 || input === null) return void 0;
2599
2636
  const columns = input.columns;
2600
2637
  if (typeof input !== "object" || !Array.isArray(columns)) {
2601
- logger.warn(`task "${slug}": items.columns precisa ser uma lista \u2014 declara\xE7\xE3o de colunas ignorada`);
2638
+ logger.warn(`items.columns precisa ser uma lista \u2014 declara\xE7\xE3o de colunas ignorada`);
2602
2639
  return void 0;
2603
2640
  }
2604
2641
  const result = [];
@@ -2608,15 +2645,15 @@ function normalizeItemColumns(slug, input, logger) {
2608
2645
  const rawLabel = typeof entry === "object" && entry !== null ? entry.label : void 0;
2609
2646
  const key = typeof rawKey === "string" ? rawKey.trim() : "";
2610
2647
  if (!KEY_RE.test(key)) {
2611
- logger.warn(`task "${slug}": coluna de item inv\xE1lida ignorada (${JSON.stringify(rawKey)}) \u2014 use letras, n\xFAmeros, ponto, h\xEDfen e underline`);
2648
+ logger.warn(`coluna de item inv\xE1lida ignorada (${JSON.stringify(rawKey)}) \u2014 use letras, n\xFAmeros, ponto, h\xEDfen e underline`);
2612
2649
  continue;
2613
2650
  }
2614
2651
  if (seen.has(key)) {
2615
- logger.warn(`task "${slug}": coluna de item "${key}" repetida ignorada`);
2652
+ logger.warn(`coluna de item "${key}" repetida ignorada`);
2616
2653
  continue;
2617
2654
  }
2618
2655
  if (result.length >= MAX_COLUMNS) {
2619
- logger.warn(`task "${slug}": no m\xE1ximo ${MAX_COLUMNS} colunas de item \u2014 "${key}" em diante ignoradas`);
2656
+ logger.warn(`no m\xE1ximo ${MAX_COLUMNS} colunas de item \u2014 "${key}" em diante ignoradas`);
2620
2657
  break;
2621
2658
  }
2622
2659
  seen.add(key);
@@ -2628,15 +2665,15 @@ function normalizeItemColumns(slug, input, logger) {
2628
2665
 
2629
2666
  // src/tasks/local.ts
2630
2667
  var LocalRunner = class {
2631
- constructor(task, deps) {
2632
- this.task = task;
2668
+ constructor(handler, deps) {
2669
+ this.handler = handler;
2633
2670
  this.deps = deps;
2634
- this.backoff = new Backoff(deps.logger, `cria\xE7\xE3o da run "${task.slug}"`);
2671
+ this.backoff = new Backoff(deps.logger, "cria\xE7\xE3o da run");
2635
2672
  this.done = new Promise((resolve2) => {
2636
2673
  this.resolveDone = resolve2;
2637
2674
  });
2638
2675
  }
2639
- task;
2676
+ handler;
2640
2677
  deps;
2641
2678
  abort = new AbortController();
2642
2679
  backoff;
@@ -2647,17 +2684,17 @@ var LocalRunner = class {
2647
2684
  try {
2648
2685
  const run = await this.createRun();
2649
2686
  if (!run) return;
2650
- this.deps.logger.debug(`run ${run.id} aberta para "${this.task.slug}" (modo start)`);
2687
+ this.deps.logger.debug(`run ${run.id} aberta (modo start)`);
2651
2688
  await executeRun({
2652
2689
  run,
2653
- task: this.task,
2690
+ handler: this.handler,
2654
2691
  api: this.deps.api,
2655
2692
  client: this.deps.client,
2656
2693
  logger: this.deps.logger,
2657
2694
  tracker: this.deps.tracker
2658
2695
  });
2659
2696
  } catch (error) {
2660
- this.deps.logger.error(`erro inesperado na task "${this.task.slug}":`, error);
2697
+ this.deps.logger.error("erro inesperado na execu\xE7\xE3o do agente:", error);
2661
2698
  } finally {
2662
2699
  this.resolveDone();
2663
2700
  }
@@ -2670,7 +2707,7 @@ var LocalRunner = class {
2670
2707
  for (; ; ) {
2671
2708
  if (this.abort.signal.aborted) return void 0;
2672
2709
  try {
2673
- return await this.deps.api.createRun({ task: this.task.slug, instance: this.deps.instanceInfo() }, this.abort.signal);
2710
+ return await this.deps.api.createRun({ instance: this.deps.instanceInfo() }, this.abort.signal);
2674
2711
  } catch (error) {
2675
2712
  if (this.abort.signal.aborted) return void 0;
2676
2713
  await abortableSleep(this.backoff.next(error), this.abort.signal);
@@ -2680,45 +2717,6 @@ var LocalRunner = class {
2680
2717
  };
2681
2718
 
2682
2719
  // 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
2720
  var RunTracker = class {
2723
2721
  constructor(api) {
2724
2722
  this.api = api;
@@ -2739,12 +2737,6 @@ var RunTracker = class {
2739
2737
  get activeCount() {
2740
2738
  return this.reporters.size;
2741
2739
  }
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
2740
  /** A única run ativa, quando há exatamente uma — o que `hobots.*` usa fora de um contexto async. */
2749
2741
  singleActive() {
2750
2742
  if (this.reporters.size !== 1) return void 0;
@@ -2833,40 +2825,13 @@ var DEFAULT_RETRIES2 = 1;
2833
2825
  var DEFAULT_RETRY_DELAY_MS2 = 5e3;
2834
2826
  var DEFAULT_CANCEL_GRACE_MS = 1e4;
2835
2827
  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
2828
  var runtime;
2849
2829
  var warnedNoRuntime = false;
2850
2830
  function hostname() {
2851
2831
  if (!isNode()) return void 0;
2852
2832
  return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
2853
2833
  }
2854
- function sameValue(a, b) {
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
- }
2834
+ function createRuntime(options, handler) {
2870
2835
  const config = resolveConfig({ clientSecret: options.clientSecret });
2871
2836
  const logger = makeLogger(options.debug ?? false);
2872
2837
  const environment = resolveEnvironmentOption(options.environment);
@@ -2874,19 +2839,19 @@ function ensureRuntime(options) {
2874
2839
  { environment, release: options.release, tags: options.tags, debug: options.debug },
2875
2840
  config
2876
2841
  );
2877
- const api = new TasksApi({ baseUrl: config.tasksBaseUrl, key: config.key, logger });
2878
- runtime = {
2842
+ const api = new TasksApi({ baseUrl: config.runsBaseUrl, key: config.key, logger });
2843
+ const rt = {
2879
2844
  options: { ...options, clientSecret: void 0 },
2880
2845
  config,
2881
2846
  client,
2882
2847
  logger,
2883
2848
  api,
2884
- registry: new TaskRegistry(),
2849
+ handler,
2885
2850
  tracker: new RunTracker(api),
2886
2851
  sampler: void 0,
2887
2852
  heartbeat: void 0,
2888
2853
  agent: void 0,
2889
- locals: /* @__PURE__ */ new Map(),
2854
+ local: void 0,
2890
2855
  instanceId: resolveInstanceId(options.instanceId),
2891
2856
  environment,
2892
2857
  pollIntervalMs: void 0,
@@ -2896,24 +2861,19 @@ function ensureRuntime(options) {
2896
2861
  uninstallGlobalHandlers: options.captureUnhandled !== false ? installGlobalHandlers(client) : void 0,
2897
2862
  uninstallShutdown: void 0
2898
2863
  };
2899
- runtime.options.clientSecret = options.clientSecret;
2900
2864
  logger.debug("inicializado", { endpoint: config.ingestEndpoint });
2901
- return runtime;
2865
+ return rt;
2902
2866
  }
2903
- function validateTask(slug, handler, fn) {
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
- }
2867
+ function validateHandler(handler, fn) {
2907
2868
  if (typeof handler !== "function") {
2908
- throw new Error(`[hobots] ${fn}('${slug}'): o handler precisa ser uma fun\xE7\xE3o`);
2869
+ throw new Error(`[hobots] ${fn}(): o handler precisa ser uma fun\xE7\xE3o \u2014 ${fn}(async (params) => { ... })`);
2909
2870
  }
2910
- return slug.trim();
2911
2871
  }
2912
2872
  function clampConcurrency(value) {
2913
2873
  if (value === void 0 || !Number.isFinite(value)) return 1;
2914
2874
  return Math.min(MAX_CONCURRENCY, Math.max(1, Math.floor(value)));
2915
2875
  }
2916
- function resolveTaskOptions(slug, options, mode, logger) {
2876
+ function resolveRunOptions(options, mode, logger) {
2917
2877
  const retries = options.retries === void 0 || !Number.isFinite(options.retries) ? DEFAULT_RETRIES2 : Math.max(0, Math.floor(options.retries));
2918
2878
  return {
2919
2879
  retries,
@@ -2923,7 +2883,7 @@ function resolveTaskOptions(slug, options, mode, logger) {
2923
2883
  logFlushIntervalMs: options.logFlushIntervalMs,
2924
2884
  cancelCheckIntervalMs: options.cancelCheckIntervalMs,
2925
2885
  cancelGraceMs: options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS,
2926
- itemColumns: normalizeItemColumns(slug, options.items, logger)
2886
+ itemColumns: normalizeItemColumns(options.items, logger)
2927
2887
  };
2928
2888
  }
2929
2889
  function instanceInfo(rt) {
@@ -2933,10 +2893,10 @@ function instanceInfo(rt) {
2933
2893
  host: hostname(),
2934
2894
  // ambiente da instância: junto do heartbeat, é como o servidor sabe onde o robô roda
2935
2895
  environment: rt.environment,
2936
- // TODAS as tasks (register + start) — o painel mostra o que o agente sabe fazer
2937
- tasks: rt.registry.allSlugs(),
2896
+ // como esta instância trabalha — o painel mostra "sob demanda" / "sempre ligado"
2897
+ mode: rt.handler.mode === "remote" ? "on_demand" : "always_on",
2938
2898
  // colunas da aba "itens" declaradas no código — a plataforma usa enquanto o usuário não personalizar
2939
- task_items: rt.registry.itemDeclarations(),
2899
+ items: rt.handler.options.itemColumns ? { attributes: rt.handler.options.itemColumns } : void 0,
2940
2900
  // o servidor calcula a tolerância de offline (3×) a partir disto em agentes que fazem
2941
2901
  // poll; clampado à faixa aceita pelo schema (1s..15min)
2942
2902
  poll_interval_ms: rt.agent ? Math.min(Math.max(rt.agent.pollIntervalMs, 1e3), 9e5) : void 0,
@@ -2949,22 +2909,22 @@ function ensureAgent(rt) {
2949
2909
  api: rt.api,
2950
2910
  client: rt.client,
2951
2911
  logger: rt.logger,
2952
- registry: rt.registry,
2912
+ handler: rt.handler,
2953
2913
  tracker: rt.tracker,
2954
2914
  instanceInfo: () => instanceInfo(rt),
2955
2915
  pollIntervalMs: rt.pollIntervalMs
2956
2916
  });
2957
2917
  return rt.agent;
2958
2918
  }
2959
- function launchLocal(rt, task) {
2960
- const runner = new LocalRunner(task, {
2919
+ function launchLocal(rt) {
2920
+ const runner = new LocalRunner(rt.handler, {
2961
2921
  api: rt.api,
2962
2922
  client: rt.client,
2963
2923
  logger: rt.logger,
2964
2924
  tracker: rt.tracker,
2965
2925
  instanceInfo: () => instanceInfo(rt)
2966
2926
  });
2967
- rt.locals.set(task.slug, runner);
2927
+ rt.local = runner;
2968
2928
  void runner.run();
2969
2929
  }
2970
2930
  async function boot(rt) {
@@ -2986,8 +2946,8 @@ async function boot(rt) {
2986
2946
  if (rt.options.installExitHandlers !== false) {
2987
2947
  rt.uninstallShutdown = installShutdownHandlers(rt.tracker);
2988
2948
  }
2989
- if (rt.registry.remoteTasks().length > 0) ensureAgent(rt).start();
2990
- for (const task of rt.registry.localTasks()) launchLocal(rt, task);
2949
+ if (rt.handler.mode === "remote") ensureAgent(rt).start();
2950
+ else launchLocal(rt);
2991
2951
  }
2992
2952
  function scheduleBoot(rt) {
2993
2953
  if (rt.bootScheduled) return;
@@ -2996,34 +2956,23 @@ function scheduleBoot(rt) {
2996
2956
  rt.bootPromise = boot(rt).catch((error) => rt.logger.error("falha ao iniciar o SDK:", error));
2997
2957
  });
2998
2958
  }
2999
- function addTask(fn, mode, task, handler, options) {
3000
- const slug = validateTask(task, handler, fn);
3001
- const rt = ensureRuntime(options);
3002
- if (mode === "remote" && options.pollIntervalMs !== void 0) {
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);
2959
+ function startAgent(fn, mode, handler, options) {
2960
+ validateHandler(handler, fn);
2961
+ if (runtime) {
2962
+ 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
2963
  }
2964
+ const logger = makeLogger(options.debug ?? false);
2965
+ const agentHandler = { mode, handler, options: resolveRunOptions(options, mode, logger) };
2966
+ const rt = createRuntime(options, agentHandler);
2967
+ if (mode === "remote" && options.pollIntervalMs !== void 0) rt.pollIntervalMs = options.pollIntervalMs;
2968
+ runtime = rt;
2969
+ scheduleBoot(rt);
3021
2970
  }
3022
- function register(task, handler, options = {}) {
3023
- addTask("register", "remote", task, handler, options);
2971
+ function register(handler, options = {}) {
2972
+ startAgent("register", "remote", handler, options);
3024
2973
  }
3025
- function start(task, handler, options = {}) {
3026
- addTask("start", "local", task, handler, options);
2974
+ function start(handler, options = {}) {
2975
+ startAgent("start", "local", handler, options);
3027
2976
  }
3028
2977
  function currentReporter() {
3029
2978
  return currentRunReporter() ?? runtime?.tracker.singleActive();
@@ -3037,7 +2986,7 @@ function toRunLogLevel(type) {
3037
2986
  function forwardAttachment(rt, attachment, message, type) {
3038
2987
  const reporter = currentReporter();
3039
2988
  if (!reporter) {
3040
- rt.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o de task");
2989
+ rt.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o");
3041
2990
  return;
3042
2991
  }
3043
2992
  const withMeta = typeof attachment === "object" && !isImageAttachment(attachment) && "image" in attachment ? image(attachment.image, { caption: attachment.caption, contentType: attachment.contentType }) : attachment;
@@ -3048,7 +2997,7 @@ function captureException(error, hint) {
3048
2997
  if (!rt) {
3049
2998
  if (!warnedNoRuntime) {
3050
2999
  warnedNoRuntime = true;
3051
- console.warn("[hobots] captureException antes de registrar uma task (register/start) \u2014 evento descartado");
3000
+ console.warn("[hobots] captureException antes de iniciar o agente (register/start) \u2014 evento descartado");
3052
3001
  }
3053
3002
  return void 0;
3054
3003
  }
@@ -3064,7 +3013,7 @@ async function close(timeoutMs = 3e4) {
3064
3013
  runtime = void 0;
3065
3014
  await rt.bootPromise;
3066
3015
  if (rt.agent) await rt.agent.stop(0);
3067
- for (const local of rt.locals.values()) local.stop();
3016
+ rt.local?.stop();
3068
3017
  const idle = await rt.tracker.waitIdle(timeoutMs);
3069
3018
  if (!idle) {
3070
3019
  rt.logger.warn(`${rt.tracker.activeCount} run(s) ainda em execu\xE7\xE3o ap\xF3s ${timeoutMs}ms \u2014 abandonando a espera`);
@@ -3082,15 +3031,14 @@ var warned = /* @__PURE__ */ new Set();
3082
3031
  function warnOutsideRun(member) {
3083
3032
  if (warned.has(member)) return;
3084
3033
  warned.add(member);
3085
- const message = `hobots.${member} usado fora de uma execu\xE7\xE3o de task \u2014 ignorado (registre a task com register()/start() e use dentro do handler)`;
3034
+ 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
3035
  const logger = activeLogger();
3087
3036
  if (logger) logger.warn(message);
3088
3037
  else console.warn(`[hobots] ${message}`);
3089
3038
  }
3090
- var FALLBACK_TASK = "hobots";
3091
3039
  var fallbackLogAt = (level) => (message, ...rest) => {
3092
3040
  const { text, image: dropped } = splitLogArgs(message, rest);
3093
- printRunLog(FALLBACK_TASK, level, dropped ? `${text} [imagem descartada]` : text);
3041
+ printRunLog(level, dropped ? `${text} [imagem descartada]` : text);
3094
3042
  };
3095
3043
  var fallbackLog = {
3096
3044
  debug: fallbackLogAt("debug"),
@@ -3107,7 +3055,7 @@ var fallbackEachSink = {
3107
3055
  advance: noop,
3108
3056
  checkCanceled: noop,
3109
3057
  isAborted: () => false,
3110
- logError: (message) => printRunLog(FALLBACK_TASK, "error", message)
3058
+ logError: (message) => printRunLog("error", message)
3111
3059
  };
3112
3060
  var fallbackItems = {
3113
3061
  add: noop,
@@ -3134,7 +3082,6 @@ function method(value) {
3134
3082
  var hobots = Object.defineProperties({}, {
3135
3083
  // ── contexto da run atual (getters) ─────────────────────────────────────────
3136
3084
  runId: getter("runId", () => ""),
3137
- task: getter("task", () => ""),
3138
3085
  params: getter("params", () => ({})),
3139
3086
  log: getter("log", () => fallbackLog),
3140
3087
  items: getter("items", () => fallbackItems),
@@ -3143,17 +3090,18 @@ var hobots = Object.defineProperties({}, {
3143
3090
  signal: getter("signal", () => neverAborts),
3144
3091
  throwIfCanceled: method(() => resolve("throwIfCanceled")?.throwIfCanceled()),
3145
3092
  sleep: method((ms) => resolve("sleep")?.sleep(ms) ?? sleep(ms)),
3093
+ flush: method((timeoutMs) => resolve("flush")?.flush(timeoutMs) ?? Promise.resolve()),
3146
3094
  retry: method((fn, options) => {
3147
3095
  const reporter = currentReporter();
3148
3096
  return reporter ? reporter.context.retry(fn, options) : retryWithBackoff(fn, options);
3149
3097
  }),
3150
3098
  // ── processo ────────────────────────────────────────────────────────────────
3151
- register: method((task, handler, options) => {
3152
- register(task, handler, options);
3099
+ register: method((handler, options) => {
3100
+ register(handler, options);
3153
3101
  installGlobalHobots();
3154
3102
  }),
3155
- start: method((task, handler, options) => {
3156
- start(task, handler, options);
3103
+ start: method((handler, options) => {
3104
+ start(handler, options);
3157
3105
  installGlobalHobots();
3158
3106
  }),
3159
3107
  close: method((timeoutMs) => close(timeoutMs)),