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/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.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 da task); copie o Client Secret do agente em Configura\xE7\xF5es \u2192 Agentes`
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
- tasksBaseUrl: `${baseUrl}/v1/tasks`
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}/runs`, {
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" || typeof body.task !== "string") {
975
- throw new TasksApiError("resposta inv\xE1lida da cria\xE7\xE3o de run (id/task ausentes)", 0);
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" || typeof run.task !== "string") continue;
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, task: run.task, params });
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}/runs/${encodeURIComponent(runId)}`, {
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}/runs/${encodeURIComponent(runId)}`, {
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}/runs/${encodeURIComponent(runId)}/items`, {
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}/runs/${encodeURIComponent(runId)}/attachments`, {
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}/runs/${encodeURIComponent(runId)}/attachments/${encodeURIComponent(attachmentId)}`,
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(task, level, message) {
1390
- const line = colorize(`[hobots:${task}] ${level.toUpperCase()}`, LOG_LEVEL_COLOR[level]);
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, task, logger, tx, flushIntervalMs, cancelCheckIntervalMs, params = {}) {
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(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). */
@@ -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 de task");
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, task, api, client, logger, tracker } = input;
2339
- const options = task.options;
2340
- const tx = new Transaction(client, `run:${run.task}`, "hobots.task", run.id);
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} (${run.task})`);
2357
+ logger.debug(`executando run ${run.id}`);
2361
2358
  try {
2362
- await runWithRunContext({ reporter }, () => invokeWithRetry(task, run, reporter, logger));
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(task, run, reporter, logger) {
2391
- const { retries, retryDelayMs } = task.options;
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(task, run, reporter, logger), {
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(task, run, reporter, logger) {
2409
- const { timeoutMs, cancelGraceMs } = task.options;
2410
- const handler = task.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.registry = options.registry;
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
- registry;
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 (tasks: ${this.registry.remoteTasks().map((task) => task.slug).join(", ")})`);
2490
+ this.logger.debug(`polling iniciado (concorr\xEAncia ${this.handler.options.concurrency})`);
2494
2491
  this.schedule(0);
2495
2492
  }
2496
- /** Uma task registrada depois do boot entra no próximo poll — que é antecipado para já. */
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 por task remota — a soma vira o `max` do poll e as tasks com slot viram o filtro. */
2524
+ /** Slots livres do agente — vira o `max` do poll (nunca reivindica trabalho para deixar parado). */
2528
2525
  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) };
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 { tasks, max } = this.freeSlots();
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. `instance.tasks` lista TODAS as
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
- 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 });
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(slug, input, logger) {
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(`task "${slug}": items.columns precisa ser uma lista \u2014 declara\xE7\xE3o de colunas ignorada`);
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(`task "${slug}": coluna de item inv\xE1lida ignorada (${JSON.stringify(rawKey)}) \u2014 use letras, n\xFAmeros, ponto, h\xEDfen e underline`);
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(`task "${slug}": coluna de item "${key}" repetida ignorada`);
2593
+ logger.warn(`coluna de item "${key}" repetida ignorada`);
2616
2594
  continue;
2617
2595
  }
2618
2596
  if (result.length >= MAX_COLUMNS) {
2619
- logger.warn(`task "${slug}": no m\xE1ximo ${MAX_COLUMNS} colunas de item \u2014 "${key}" em diante ignoradas`);
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(task, deps) {
2632
- this.task = task;
2609
+ constructor(handler, deps) {
2610
+ this.handler = handler;
2633
2611
  this.deps = deps;
2634
- this.backoff = new Backoff(deps.logger, `cria\xE7\xE3o da run "${task.slug}"`);
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
- task;
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 para "${this.task.slug}" (modo start)`);
2628
+ this.deps.logger.debug(`run ${run.id} aberta (modo start)`);
2651
2629
  await executeRun({
2652
2630
  run,
2653
- task: this.task,
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(`erro inesperado na task "${this.task.slug}":`, 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({ task: this.task.slug, instance: this.deps.instanceInfo() }, this.abort.signal);
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 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
- }
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.tasksBaseUrl, key: config.key, logger });
2878
- runtime = {
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
- registry: new TaskRegistry(),
2790
+ handler,
2885
2791
  tracker: new RunTracker(api),
2886
2792
  sampler: void 0,
2887
2793
  heartbeat: void 0,
2888
2794
  agent: void 0,
2889
- locals: /* @__PURE__ */ new Map(),
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 runtime;
2806
+ return rt;
2902
2807
  }
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
- }
2808
+ function validateHandler(handler, fn) {
2907
2809
  if (typeof handler !== "function") {
2908
- throw new Error(`[hobots] ${fn}('${slug}'): o handler precisa ser uma fun\xE7\xE3o`);
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 resolveTaskOptions(slug, options, mode, logger) {
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(slug, options.items, logger)
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
- // TODAS as tasks (register + start) — o painel mostra o que o agente sabe fazer
2937
- tasks: rt.registry.allSlugs(),
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
- task_items: rt.registry.itemDeclarations(),
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
- registry: rt.registry,
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, task) {
2960
- const runner = new LocalRunner(task, {
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.locals.set(task.slug, runner);
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.registry.remoteTasks().length > 0) ensureAgent(rt).start();
2990
- for (const task of rt.registry.localTasks()) launchLocal(rt, task);
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 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);
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(task, handler, options = {}) {
3023
- addTask("register", "remote", task, handler, options);
2912
+ function register(handler, options = {}) {
2913
+ startAgent("register", "remote", handler, options);
3024
2914
  }
3025
- function start(task, handler, options = {}) {
3026
- addTask("start", "local", task, handler, options);
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 de task");
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 registrar uma task (register/start) \u2014 evento descartado");
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
- for (const local of rt.locals.values()) local.stop();
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 de task \u2014 ignorado (registre a task com register()/start() e use dentro do handler)`;
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(FALLBACK_TASK, level, dropped ? `${text} [imagem descartada]` : text);
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(FALLBACK_TASK, "error", message)
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((task, handler, options) => {
3152
- register(task, handler, options);
3039
+ register: method((handler, options) => {
3040
+ register(handler, options);
3153
3041
  installGlobalHobots();
3154
3042
  }),
3155
- start: method((task, handler, options) => {
3156
- start(task, handler, options);
3043
+ start: method((handler, options) => {
3044
+ start(handler, options);
3157
3045
  installGlobalHobots();
3158
3046
  }),
3159
3047
  close: method((timeoutMs) => close(timeoutMs)),