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