hobots 0.3.1 → 1.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.
Files changed (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +346 -0
  3. package/dist/chunk-FW32OR7V.js +75 -0
  4. package/dist/chunk-FW32OR7V.js.map +1 -0
  5. package/dist/index.cjs +764 -0
  6. package/dist/index.cjs.map +1 -0
  7. package/dist/index.d.cts +187 -0
  8. package/dist/index.d.ts +187 -0
  9. package/dist/index.js +691 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/manager/protocol.cjs +19 -0
  12. package/dist/manager/protocol.cjs.map +1 -0
  13. package/dist/manager/protocol.d.cts +72 -0
  14. package/dist/manager/protocol.d.ts +72 -0
  15. package/dist/manager/protocol.js +1 -0
  16. package/dist/manager/protocol.js.map +1 -0
  17. package/dist/manager.cjs +519 -0
  18. package/dist/manager.cjs.map +1 -0
  19. package/dist/manager.d.cts +118 -0
  20. package/dist/manager.d.ts +118 -0
  21. package/dist/manager.js +464 -0
  22. package/dist/manager.js.map +1 -0
  23. package/dist/protocol.cjs +19 -0
  24. package/dist/protocol.cjs.map +1 -0
  25. package/dist/protocol.d.cts +126 -0
  26. package/dist/protocol.d.ts +126 -0
  27. package/dist/protocol.js +1 -0
  28. package/dist/protocol.js.map +1 -0
  29. package/dist/utils-LReLqjzN.d.cts +7 -0
  30. package/dist/utils-LReLqjzN.d.ts +7 -0
  31. package/docs/README.md +39 -0
  32. package/docs/api-reference.md +140 -0
  33. package/docs/getting-started.md +88 -0
  34. package/docs/manager.md +90 -0
  35. package/docs/monitor.md +136 -0
  36. package/docs/protocol.md +87 -0
  37. package/package.json +50 -2
  38. package/auth/index.d.ts +0 -10
  39. package/csv/index.d.ts +0 -9
  40. package/datasets/index.d.ts +0 -2
  41. package/db/drizzle.config.d.ts +0 -8
  42. package/db/index.d.ts +0 -9
  43. package/db/migrate.d.ts +0 -3
  44. package/db/schema.d.ts +0 -215
  45. package/db/storages/base.d.ts +0 -23
  46. package/db/storages/index.d.ts +0 -3
  47. package/db/storages/job.d.ts +0 -233
  48. package/exceptions/break.d.ts +0 -6
  49. package/exceptions/ignore.d.ts +0 -6
  50. package/exceptions/index.d.ts +0 -4
  51. package/exceptions/warning.d.ts +0 -6
  52. package/foreach/foreach.d.ts +0 -7
  53. package/foreach/index.d.ts +0 -5
  54. package/foreach/item.d.ts +0 -13
  55. package/hobots/samples/bot.d.ts +0 -2
  56. package/hobots/samples/index.d.ts +0 -2
  57. package/hobots/src/auth/index.d.ts +0 -10
  58. package/hobots/src/csv/index.d.ts +0 -9
  59. package/hobots/src/datasets/index.d.ts +0 -2
  60. package/hobots/src/db/drizzle.config.d.ts +0 -8
  61. package/hobots/src/db/index.d.ts +0 -9
  62. package/hobots/src/db/migrate.d.ts +0 -3
  63. package/hobots/src/db/schema.d.ts +0 -215
  64. package/hobots/src/db/storages/base.d.ts +0 -23
  65. package/hobots/src/db/storages/index.d.ts +0 -3
  66. package/hobots/src/db/storages/job.d.ts +0 -233
  67. package/hobots/src/exceptions/break.d.ts +0 -6
  68. package/hobots/src/exceptions/ignore.d.ts +0 -6
  69. package/hobots/src/exceptions/index.d.ts +0 -4
  70. package/hobots/src/exceptions/warning.d.ts +0 -6
  71. package/hobots/src/foreach/foreach.d.ts +0 -7
  72. package/hobots/src/foreach/index.d.ts +0 -5
  73. package/hobots/src/foreach/item.d.ts +0 -13
  74. package/hobots/src/index.d.ts +0 -9
  75. package/hobots/src/reports/data-report.d.ts +0 -9
  76. package/hobots/src/reports/index.d.ts +0 -3
  77. package/hobots/src/reports/job-report.d.ts +0 -22
  78. package/hobots/src/setup.d.ts +0 -20
  79. package/hobots/src/sheets/index.d.ts +0 -13
  80. package/hobots/src/types/hobots-types.d.ts +0 -20
  81. package/hobots/src/utils/date.d.ts +0 -117
  82. package/hobots/src/utils/dist.d.ts +0 -8
  83. package/hobots/src/utils/garbage.d.ts +0 -2
  84. package/hobots/src/utils/index.d.ts +0 -10
  85. package/hobots/src/utils/logger.d.ts +0 -21
  86. package/hobots/src/utils/manifest.d.ts +0 -2
  87. package/hobots/src/utils/path.d.ts +0 -13
  88. package/hobots/src/utils/publish.d.ts +0 -2
  89. package/hobots/src/utils/retry.d.ts +0 -5
  90. package/hobots/src/utils/text.d.ts +0 -74
  91. package/hobots/src/utils/timer.d.ts +0 -3
  92. package/index.d.ts +0 -9
  93. package/index.js +0 -131866
  94. package/index.js.map +0 -1
  95. package/reports/data-report.d.ts +0 -9
  96. package/reports/index.d.ts +0 -3
  97. package/reports/job-report.d.ts +0 -22
  98. package/setup.d.ts +0 -20
  99. package/sheets/index.d.ts +0 -13
  100. package/types/hobots-types.d.ts +0 -20
  101. package/utils/date.d.ts +0 -117
  102. package/utils/dist.d.ts +0 -8
  103. package/utils/garbage.d.ts +0 -2
  104. package/utils/index.d.ts +0 -10
  105. package/utils/logger.d.ts +0 -21
  106. package/utils/manifest.d.ts +0 -2
  107. package/utils/path.d.ts +0 -13
  108. package/utils/publish.d.ts +0 -2
  109. package/utils/retry.d.ts +0 -5
  110. package/utils/src/path.d.ts +0 -14
  111. package/utils/text.d.ts +0 -74
  112. package/utils/timer.d.ts +0 -3
@@ -0,0 +1,118 @@
1
+ import { RunLogLevel, ManagerPollRequest, ManagerPollResponse, ManagerRunUpdateRequest } from './manager/protocol.cjs';
2
+ export { ManagerAgentInfo, ManagerRunDescriptor, ManagerRunError, ManagerRunStatus, ManagerRunUpdateResponse, ManagerRunUpdateStatus, RunLogEntry } from './manager/protocol.cjs';
3
+ import { L as Logger } from './utils-LReLqjzN.cjs';
4
+
5
+ interface ManagerInitOptions {
6
+ /** Ex.: https://<managerKey>@monitor.hobots.app/<projectSlug> */
7
+ dsn: string;
8
+ /** Intervalo entre polls sem trabalho. Default: 5_000 (mínimo 1_000) */
9
+ pollIntervalMs?: number;
10
+ /** Runs simultâneas (1..5). Default: 1 (sequencial) */
11
+ concurrency?: number;
12
+ /** Nome do agente exibido no dashboard. Default: hostname */
13
+ agentName?: string;
14
+ /**
15
+ * Timeout client-side por run. O handler não é morto — a run é reportada como
16
+ * falha e o slot é liberado. Default: sem timeout (o servidor é o backstop).
17
+ */
18
+ taskTimeoutMs?: number;
19
+ /** Intervalo do flush incremental de logs. Default: 2_000 */
20
+ logFlushIntervalMs?: number;
21
+ /** Loga atividade do SDK no console */
22
+ debug?: boolean;
23
+ }
24
+ interface RunContext {
25
+ runId: string;
26
+ task: string;
27
+ /** Log bufferizado — enviado ao servidor em PATCHes incrementais com throttle */
28
+ log(level: RunLogLevel, message: string): void;
29
+ /** Dispara em cancelamento (run cancelada no app), timeout ou shutdown — cancelamento cooperativo */
30
+ signal: AbortSignal;
31
+ }
32
+ type ManagerTaskHandler<P = Record<string, unknown>, R = unknown> = (params: P, ctx: RunContext) => R | Promise<R>;
33
+
34
+ /** Inicializa o agente Manager. Chame uma vez; depois register() e start(). */
35
+ declare function init(options: ManagerInitOptions): void;
36
+ /** Registra o handler de uma task (o task_slug configurado no formulário do app). */
37
+ declare function register<P = Record<string, unknown>, R = unknown>(task: string, handler: ManagerTaskHandler<P, R>): void;
38
+ /**
39
+ * Inicia o polling. O timer NÃO é unref'd: o processo fica vivo aguardando
40
+ * trabalho até stop() — o inverso deliberado do Monitor.
41
+ */
42
+ declare function start(): void;
43
+ /** Para o polling e aguarda a run atual terminar (até timeoutMs). */
44
+ declare function stop(timeoutMs?: number): Promise<boolean>;
45
+
46
+ /**
47
+ * Loop de polling do Manager: busca runs pendentes, executa os handlers registrados
48
+ * e reporta status/logs/result. Ao contrário do Monitor, o timer de polling NÃO é
49
+ * unref'd — um agente iniciado mantém o processo vivo até stop().
50
+ */
51
+ declare class ManagerAgent {
52
+ private readonly options;
53
+ private readonly registry;
54
+ readonly logger: Logger;
55
+ private readonly api;
56
+ private readonly pollIntervalMs;
57
+ private readonly concurrency;
58
+ private readonly agentName?;
59
+ private readonly taskTimeoutMs?;
60
+ private readonly logFlushIntervalMs?;
61
+ private state;
62
+ private timer;
63
+ private pollAbort;
64
+ private consecutiveFailures;
65
+ private consecutiveAuthFailures;
66
+ private readonly activeRuns;
67
+ private readonly reporters;
68
+ constructor(options: ManagerInitOptions, registry: Map<string, ManagerTaskHandler>);
69
+ get activeCount(): number;
70
+ start(): void;
71
+ /** Para o polling e aguarda as runs ativas (até timeoutMs). Retorna true se tudo terminou a tempo. */
72
+ stop(timeoutMs?: number): Promise<boolean>;
73
+ /** Timer intencionalmente NÃO unref'd: o agente é a razão do processo existir. */
74
+ private schedule;
75
+ private tick;
76
+ private delayAfterPollError;
77
+ private dispatch;
78
+ private executeRun;
79
+ private invokeWithTimeout;
80
+ }
81
+
82
+ declare class ManagerApiError extends Error {
83
+ readonly status: number;
84
+ constructor(message: string, status: number);
85
+ }
86
+ interface ManagerApiOptions {
87
+ baseUrl: string;
88
+ key: string;
89
+ logger: Logger;
90
+ }
91
+ /** Cliente HTTP do agente — bearer auth, retry com backoff nos updates. */
92
+ declare class ManagerApi {
93
+ private readonly baseUrl;
94
+ private readonly key;
95
+ private readonly logger;
96
+ constructor(options: ManagerApiOptions);
97
+ private headers;
98
+ /** Um request só — o loop de polling é quem decide o backoff entre tentativas. */
99
+ poll(request: ManagerPollRequest, signal?: AbortSignal): Promise<ManagerPollResponse>;
100
+ /**
101
+ * PATCH de status/logs com retry em falha de rede/5xx/429.
102
+ * 4xx é definitivo e vira ManagerApiError imediato (409 = run cancelada/transição inválida).
103
+ */
104
+ updateRun(runId: string, update: ManagerRunUpdateRequest): Promise<void>;
105
+ }
106
+
107
+ interface ParsedManagerDsn {
108
+ /** Chave secreta do agente (enviada como Authorization: Bearer, nunca na URL) */
109
+ key: string;
110
+ projectSlug: string;
111
+ origin: string;
112
+ /** Base derivada: <origin>/api/manager */
113
+ baseUrl: string;
114
+ }
115
+ /** Formato: https://<managerKey>@host[:porta]/<projectSlug> */
116
+ declare function parseManagerDsn(dsn: string): ParsedManagerDsn;
117
+
118
+ export { ManagerAgent, ManagerApi, ManagerApiError, type ManagerInitOptions, ManagerPollRequest, ManagerPollResponse, ManagerRunUpdateRequest, type ManagerTaskHandler, type ParsedManagerDsn, type RunContext, RunLogLevel, init, parseManagerDsn, register, start, stop };
@@ -0,0 +1,118 @@
1
+ import { RunLogLevel, ManagerPollRequest, ManagerPollResponse, ManagerRunUpdateRequest } from './manager/protocol.js';
2
+ export { ManagerAgentInfo, ManagerRunDescriptor, ManagerRunError, ManagerRunStatus, ManagerRunUpdateResponse, ManagerRunUpdateStatus, RunLogEntry } from './manager/protocol.js';
3
+ import { L as Logger } from './utils-LReLqjzN.js';
4
+
5
+ interface ManagerInitOptions {
6
+ /** Ex.: https://<managerKey>@monitor.hobots.app/<projectSlug> */
7
+ dsn: string;
8
+ /** Intervalo entre polls sem trabalho. Default: 5_000 (mínimo 1_000) */
9
+ pollIntervalMs?: number;
10
+ /** Runs simultâneas (1..5). Default: 1 (sequencial) */
11
+ concurrency?: number;
12
+ /** Nome do agente exibido no dashboard. Default: hostname */
13
+ agentName?: string;
14
+ /**
15
+ * Timeout client-side por run. O handler não é morto — a run é reportada como
16
+ * falha e o slot é liberado. Default: sem timeout (o servidor é o backstop).
17
+ */
18
+ taskTimeoutMs?: number;
19
+ /** Intervalo do flush incremental de logs. Default: 2_000 */
20
+ logFlushIntervalMs?: number;
21
+ /** Loga atividade do SDK no console */
22
+ debug?: boolean;
23
+ }
24
+ interface RunContext {
25
+ runId: string;
26
+ task: string;
27
+ /** Log bufferizado — enviado ao servidor em PATCHes incrementais com throttle */
28
+ log(level: RunLogLevel, message: string): void;
29
+ /** Dispara em cancelamento (run cancelada no app), timeout ou shutdown — cancelamento cooperativo */
30
+ signal: AbortSignal;
31
+ }
32
+ type ManagerTaskHandler<P = Record<string, unknown>, R = unknown> = (params: P, ctx: RunContext) => R | Promise<R>;
33
+
34
+ /** Inicializa o agente Manager. Chame uma vez; depois register() e start(). */
35
+ declare function init(options: ManagerInitOptions): void;
36
+ /** Registra o handler de uma task (o task_slug configurado no formulário do app). */
37
+ declare function register<P = Record<string, unknown>, R = unknown>(task: string, handler: ManagerTaskHandler<P, R>): void;
38
+ /**
39
+ * Inicia o polling. O timer NÃO é unref'd: o processo fica vivo aguardando
40
+ * trabalho até stop() — o inverso deliberado do Monitor.
41
+ */
42
+ declare function start(): void;
43
+ /** Para o polling e aguarda a run atual terminar (até timeoutMs). */
44
+ declare function stop(timeoutMs?: number): Promise<boolean>;
45
+
46
+ /**
47
+ * Loop de polling do Manager: busca runs pendentes, executa os handlers registrados
48
+ * e reporta status/logs/result. Ao contrário do Monitor, o timer de polling NÃO é
49
+ * unref'd — um agente iniciado mantém o processo vivo até stop().
50
+ */
51
+ declare class ManagerAgent {
52
+ private readonly options;
53
+ private readonly registry;
54
+ readonly logger: Logger;
55
+ private readonly api;
56
+ private readonly pollIntervalMs;
57
+ private readonly concurrency;
58
+ private readonly agentName?;
59
+ private readonly taskTimeoutMs?;
60
+ private readonly logFlushIntervalMs?;
61
+ private state;
62
+ private timer;
63
+ private pollAbort;
64
+ private consecutiveFailures;
65
+ private consecutiveAuthFailures;
66
+ private readonly activeRuns;
67
+ private readonly reporters;
68
+ constructor(options: ManagerInitOptions, registry: Map<string, ManagerTaskHandler>);
69
+ get activeCount(): number;
70
+ start(): void;
71
+ /** Para o polling e aguarda as runs ativas (até timeoutMs). Retorna true se tudo terminou a tempo. */
72
+ stop(timeoutMs?: number): Promise<boolean>;
73
+ /** Timer intencionalmente NÃO unref'd: o agente é a razão do processo existir. */
74
+ private schedule;
75
+ private tick;
76
+ private delayAfterPollError;
77
+ private dispatch;
78
+ private executeRun;
79
+ private invokeWithTimeout;
80
+ }
81
+
82
+ declare class ManagerApiError extends Error {
83
+ readonly status: number;
84
+ constructor(message: string, status: number);
85
+ }
86
+ interface ManagerApiOptions {
87
+ baseUrl: string;
88
+ key: string;
89
+ logger: Logger;
90
+ }
91
+ /** Cliente HTTP do agente — bearer auth, retry com backoff nos updates. */
92
+ declare class ManagerApi {
93
+ private readonly baseUrl;
94
+ private readonly key;
95
+ private readonly logger;
96
+ constructor(options: ManagerApiOptions);
97
+ private headers;
98
+ /** Um request só — o loop de polling é quem decide o backoff entre tentativas. */
99
+ poll(request: ManagerPollRequest, signal?: AbortSignal): Promise<ManagerPollResponse>;
100
+ /**
101
+ * PATCH de status/logs com retry em falha de rede/5xx/429.
102
+ * 4xx é definitivo e vira ManagerApiError imediato (409 = run cancelada/transição inválida).
103
+ */
104
+ updateRun(runId: string, update: ManagerRunUpdateRequest): Promise<void>;
105
+ }
106
+
107
+ interface ParsedManagerDsn {
108
+ /** Chave secreta do agente (enviada como Authorization: Bearer, nunca na URL) */
109
+ key: string;
110
+ projectSlug: string;
111
+ origin: string;
112
+ /** Base derivada: <origin>/api/manager */
113
+ baseUrl: string;
114
+ }
115
+ /** Formato: https://<managerKey>@host[:porta]/<projectSlug> */
116
+ declare function parseManagerDsn(dsn: string): ParsedManagerDsn;
117
+
118
+ export { ManagerAgent, ManagerApi, ManagerApiError, type ManagerInitOptions, ManagerPollRequest, ManagerPollResponse, ManagerRunUpdateRequest, type ManagerTaskHandler, type ParsedManagerDsn, type RunContext, RunLogLevel, init, parseManagerDsn, register, start, stop };
@@ -0,0 +1,464 @@
1
+ import {
2
+ SDK_VERSION,
3
+ isNode,
4
+ makeLogger,
5
+ nowISO,
6
+ parseDsnUrl,
7
+ sleep,
8
+ unrefTimer
9
+ } from "./chunk-FW32OR7V.js";
10
+
11
+ // src/manager/api.ts
12
+ var RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
13
+ var ManagerApiError = class extends Error {
14
+ constructor(message, status) {
15
+ super(message);
16
+ this.status = status;
17
+ this.name = "ManagerApiError";
18
+ }
19
+ };
20
+ var ManagerApi = class {
21
+ baseUrl;
22
+ key;
23
+ logger;
24
+ constructor(options) {
25
+ this.baseUrl = options.baseUrl;
26
+ this.key = options.key;
27
+ this.logger = options.logger;
28
+ }
29
+ headers() {
30
+ return {
31
+ "content-type": "application/json",
32
+ authorization: `Bearer ${this.key}`
33
+ };
34
+ }
35
+ /** Um request só — o loop de polling é quem decide o backoff entre tentativas. */
36
+ async poll(request, signal) {
37
+ const response = await fetch(`${this.baseUrl}/poll`, {
38
+ method: "POST",
39
+ headers: this.headers(),
40
+ body: JSON.stringify(request),
41
+ signal
42
+ });
43
+ if (!response.ok) {
44
+ throw new ManagerApiError(`poll falhou (HTTP ${response.status})`, response.status);
45
+ }
46
+ return await response.json();
47
+ }
48
+ /**
49
+ * PATCH de status/logs com retry em falha de rede/5xx/429.
50
+ * 4xx é definitivo e vira ManagerApiError imediato (409 = run cancelada/transição inválida).
51
+ */
52
+ async updateRun(runId, update) {
53
+ for (let attempt = 0; ; attempt++) {
54
+ let retriable;
55
+ try {
56
+ const response = await fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
57
+ method: "PATCH",
58
+ headers: this.headers(),
59
+ body: JSON.stringify(update)
60
+ });
61
+ if (response.ok) return;
62
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
63
+ throw new ManagerApiError(`update rejeitado (HTTP ${response.status})`, response.status);
64
+ }
65
+ retriable = new Error(`HTTP ${response.status}`);
66
+ } catch (error) {
67
+ if (error instanceof ManagerApiError) throw error;
68
+ retriable = error;
69
+ }
70
+ const delay = RETRY_DELAYS_MS[attempt];
71
+ if (delay === void 0) {
72
+ throw new Error(`update da run ${runId} descartado ap\xF3s retries: ${String(retriable)}`);
73
+ }
74
+ this.logger.warn(`falha no update da run ${runId} (${String(retriable)}), retry em ${delay}ms`);
75
+ await sleep(delay);
76
+ }
77
+ }
78
+ };
79
+
80
+ // src/manager/context.ts
81
+ var DEFAULT_LOG_FLUSH_INTERVAL_MS = 2e3;
82
+ var MAX_LOG_MESSAGE_CHARS = 1e4;
83
+ var MAX_LOGS_PER_RUN = 500;
84
+ var MAX_LOGS_PER_PATCH = 100;
85
+ var FLUSH_AT_BUFFERED = 20;
86
+ var MAX_RESULT_CHARS = 1e5;
87
+ var RunReporter = class {
88
+ constructor(api, runId, task, logger, flushIntervalMs) {
89
+ this.api = api;
90
+ this.runId = runId;
91
+ this.logger = logger;
92
+ this.flushIntervalMs = flushIntervalMs ?? DEFAULT_LOG_FLUSH_INTERVAL_MS;
93
+ this.context = {
94
+ runId,
95
+ task,
96
+ log: (level, message) => this.log(level, message),
97
+ signal: this.abortController.signal
98
+ };
99
+ }
100
+ context;
101
+ buffer = [];
102
+ loggedCount = 0;
103
+ droppedCount = 0;
104
+ dead = false;
105
+ chain = Promise.resolve();
106
+ timer;
107
+ abortController = new AbortController();
108
+ flushIntervalMs;
109
+ get isDead() {
110
+ return this.dead;
111
+ }
112
+ /** Aborta o ctx.signal sem matar o reporter (timeout/shutdown ainda reportam o terminal). */
113
+ abort() {
114
+ if (!this.abortController.signal.aborted) this.abortController.abort();
115
+ }
116
+ log(level, message) {
117
+ if (this.dead) return;
118
+ if (this.loggedCount >= MAX_LOGS_PER_RUN) {
119
+ this.droppedCount++;
120
+ return;
121
+ }
122
+ this.loggedCount++;
123
+ const truncated = message.length > MAX_LOG_MESSAGE_CHARS ? `${message.slice(0, MAX_LOG_MESSAGE_CHARS)}\u2026 [truncado]` : message;
124
+ this.buffer.push({ ts: nowISO(), level, message: truncated });
125
+ if (this.buffer.length >= FLUSH_AT_BUFFERED) {
126
+ this.flushLogs();
127
+ } else {
128
+ this.scheduleFlush();
129
+ }
130
+ }
131
+ scheduleFlush() {
132
+ if (this.timer) return;
133
+ this.timer = setTimeout(() => {
134
+ this.timer = void 0;
135
+ this.flushLogs();
136
+ }, this.flushIntervalMs);
137
+ unrefTimer(this.timer);
138
+ }
139
+ clearFlushTimer() {
140
+ if (this.timer) {
141
+ clearTimeout(this.timer);
142
+ this.timer = void 0;
143
+ }
144
+ }
145
+ takeLogs() {
146
+ const logs = this.buffer.splice(0, this.buffer.length);
147
+ if (this.droppedCount > 0) {
148
+ logs.push({
149
+ ts: nowISO(),
150
+ level: "warning",
151
+ message: `${this.droppedCount} logs descartados (limite de ${MAX_LOGS_PER_RUN} por execu\xE7\xE3o)`
152
+ });
153
+ this.droppedCount = 0;
154
+ }
155
+ return logs;
156
+ }
157
+ /** Flush incremental: envia o buffer como status 'running' (em chunks). */
158
+ flushLogs() {
159
+ this.clearFlushTimer();
160
+ if (this.dead || this.buffer.length === 0) return;
161
+ const logs = this.buffer.splice(0, this.buffer.length);
162
+ for (let i = 0; i < logs.length; i += MAX_LOGS_PER_PATCH) {
163
+ this.enqueue({ status: "running", logs: logs.slice(i, i + MAX_LOGS_PER_PATCH) });
164
+ }
165
+ }
166
+ /** Primeiro PATCH (claimed → running). Retorna false se a run já morreu (cancelada). */
167
+ async reportRunning() {
168
+ this.enqueue({ status: "running" });
169
+ await this.chain;
170
+ return !this.dead;
171
+ }
172
+ reportSuccess(result) {
173
+ this.clearFlushTimer();
174
+ this.enqueue({ status: "succeeded", result: serializeResult(result, this.logger), logs: this.takeLogs() });
175
+ }
176
+ reportFailure(error) {
177
+ this.clearFlushTimer();
178
+ this.enqueue({ status: "failed", error, logs: this.takeLogs() });
179
+ }
180
+ /** Aguarda a fila de PATCHes esvaziar (chame depois do terminal). */
181
+ finalize() {
182
+ return this.chain;
183
+ }
184
+ enqueue(update) {
185
+ if (this.dead) return;
186
+ this.chain = this.chain.then(async () => {
187
+ if (this.dead) return;
188
+ const payload = update.logs && update.logs.length === 0 ? { ...update, logs: void 0 } : update;
189
+ try {
190
+ await this.api.updateRun(this.runId, payload);
191
+ } catch (error) {
192
+ if (error instanceof ManagerApiError && (error.status === 409 || error.status === 404)) {
193
+ this.dead = true;
194
+ this.abort();
195
+ this.buffer = [];
196
+ this.logger.warn(`run ${this.runId} cancelada/removida no servidor \u2014 reporte suprimido`);
197
+ return;
198
+ }
199
+ this.logger.error(`update da run ${this.runId} perdido:`, error);
200
+ }
201
+ });
202
+ }
203
+ };
204
+ function serializeResult(result, logger) {
205
+ if (result === void 0) return void 0;
206
+ let json;
207
+ try {
208
+ json = JSON.stringify(result);
209
+ } catch {
210
+ logger.warn("result n\xE3o serializ\xE1vel (circular/BigInt?) \u2014 descartado");
211
+ return { __truncated: true, message: "resultado n\xE3o serializ\xE1vel e foi descartado pelo SDK" };
212
+ }
213
+ if (json === void 0) return void 0;
214
+ if (json.length > MAX_RESULT_CHARS) {
215
+ logger.warn(`result excede ${MAX_RESULT_CHARS} chars \u2014 descartado`);
216
+ return { __truncated: true, message: "resultado excede 100KB e foi descartado pelo SDK" };
217
+ }
218
+ return result;
219
+ }
220
+
221
+ // src/manager/dsn.ts
222
+ function parseManagerDsn(dsn) {
223
+ const { key, path, origin } = parseDsnUrl(dsn, "[hobots-manager]");
224
+ return {
225
+ key,
226
+ projectSlug: path,
227
+ origin,
228
+ baseUrl: `${origin}/api/manager`
229
+ };
230
+ }
231
+
232
+ // src/manager/agent.ts
233
+ var DEFAULT_POLL_INTERVAL_MS = 5e3;
234
+ var MIN_POLL_INTERVAL_MS = 1e3;
235
+ var MAX_CONCURRENCY = 5;
236
+ var POLL_RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
237
+ var AUTH_FAILURE_INTERVAL_MS = 3e4;
238
+ function defaultAgentName() {
239
+ if (!isNode()) return void 0;
240
+ return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
241
+ }
242
+ var ManagerAgent = class {
243
+ constructor(options, registry2) {
244
+ this.options = options;
245
+ this.registry = registry2;
246
+ this.logger = makeLogger(options.debug ?? false, "[hobots-manager]");
247
+ const dsn = parseManagerDsn(options.dsn);
248
+ this.api = new ManagerApi({ baseUrl: dsn.baseUrl, key: dsn.key, logger: this.logger });
249
+ this.pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
250
+ this.concurrency = Math.min(MAX_CONCURRENCY, Math.max(1, options.concurrency ?? 1));
251
+ this.agentName = options.agentName ?? defaultAgentName();
252
+ this.taskTimeoutMs = options.taskTimeoutMs;
253
+ this.logFlushIntervalMs = options.logFlushIntervalMs;
254
+ this.logger.debug("agente configurado", { baseUrl: dsn.baseUrl, projectSlug: dsn.projectSlug });
255
+ }
256
+ logger;
257
+ api;
258
+ pollIntervalMs;
259
+ concurrency;
260
+ agentName;
261
+ taskTimeoutMs;
262
+ logFlushIntervalMs;
263
+ state = "idle";
264
+ timer;
265
+ pollAbort;
266
+ consecutiveFailures = 0;
267
+ consecutiveAuthFailures = 0;
268
+ activeRuns = /* @__PURE__ */ new Set();
269
+ reporters = /* @__PURE__ */ new Set();
270
+ get activeCount() {
271
+ return this.activeRuns.size;
272
+ }
273
+ start() {
274
+ if (this.state === "started") return;
275
+ if (this.state === "stopping") {
276
+ throw new Error("[hobots-manager] start() chamado durante stop() \u2014 aguarde o stop terminar");
277
+ }
278
+ if (this.registry.size === 0) {
279
+ throw new Error("[hobots-manager] nenhuma task registrada \u2014 chame register() antes de start()");
280
+ }
281
+ this.state = "started";
282
+ this.logger.debug(`polling iniciado (tasks: ${[...this.registry.keys()].join(", ")})`);
283
+ this.schedule(0);
284
+ }
285
+ /** Para o polling e aguarda as runs ativas (até timeoutMs). Retorna true se tudo terminou a tempo. */
286
+ async stop(timeoutMs = 3e4) {
287
+ if (this.state === "idle") return true;
288
+ this.state = "stopping";
289
+ if (this.timer) {
290
+ clearTimeout(this.timer);
291
+ this.timer = void 0;
292
+ }
293
+ this.pollAbort?.abort();
294
+ const all = Promise.allSettled([...this.activeRuns]).then(() => true);
295
+ const finished = timeoutMs > 0 ? await Promise.race([all, sleep(timeoutMs).then(() => false)]) : false;
296
+ if (!finished && this.activeRuns.size > 0) {
297
+ this.logger.warn(`${this.activeRuns.size} run(s) ainda em execu\xE7\xE3o ap\xF3s ${timeoutMs}ms \u2014 abandonando a espera`);
298
+ for (const reporter of this.reporters) reporter.abort();
299
+ }
300
+ this.state = "idle";
301
+ return finished;
302
+ }
303
+ /** Timer intencionalmente NÃO unref'd: o agente é a razão do processo existir. */
304
+ schedule(delayMs) {
305
+ if (this.state !== "started" || this.timer) return;
306
+ this.timer = setTimeout(() => {
307
+ this.timer = void 0;
308
+ void this.tick();
309
+ }, delayMs);
310
+ }
311
+ async tick() {
312
+ if (this.state !== "started") return;
313
+ if (this.activeRuns.size >= this.concurrency) {
314
+ this.schedule(this.pollIntervalMs);
315
+ return;
316
+ }
317
+ this.pollAbort = new AbortController();
318
+ try {
319
+ const response = await this.api.poll(
320
+ {
321
+ agent: {
322
+ name: this.agentName,
323
+ version: SDK_VERSION,
324
+ host: defaultAgentName(),
325
+ tasks: [...this.registry.keys()]
326
+ },
327
+ max: Math.min(MAX_CONCURRENCY, this.concurrency - this.activeRuns.size),
328
+ tasks: [...this.registry.keys()]
329
+ },
330
+ this.pollAbort.signal
331
+ );
332
+ this.consecutiveFailures = 0;
333
+ this.consecutiveAuthFailures = 0;
334
+ for (const run of response.runs) {
335
+ this.dispatch(run);
336
+ }
337
+ this.schedule(response.runs.length > 0 ? 0 : this.pollIntervalMs);
338
+ } catch (error) {
339
+ if (this.state !== "started") return;
340
+ this.schedule(this.delayAfterPollError(error));
341
+ } finally {
342
+ this.pollAbort = void 0;
343
+ }
344
+ }
345
+ delayAfterPollError(error) {
346
+ if (error instanceof ManagerApiError && error.status === 401) {
347
+ this.consecutiveAuthFailures++;
348
+ if (this.consecutiveAuthFailures === 1 || this.consecutiveAuthFailures % 10 === 0) {
349
+ this.logger.error("chave do agente rejeitada (401) \u2014 verifique a chave em /manager/agents");
350
+ }
351
+ return Math.max(this.pollIntervalMs, AUTH_FAILURE_INTERVAL_MS);
352
+ }
353
+ this.consecutiveFailures++;
354
+ const ladder = POLL_RETRY_DELAYS_MS[Math.min(this.consecutiveFailures - 1, POLL_RETRY_DELAYS_MS.length - 1)];
355
+ const delay = Math.min(Math.max(ladder, 0), Math.max(this.pollIntervalMs, 16e3));
356
+ this.logger.warn(`poll falhou (${String(error)}), pr\xF3xima tentativa em ${delay}ms`);
357
+ return delay;
358
+ }
359
+ dispatch(run) {
360
+ const promise = this.executeRun(run).catch((error) => this.logger.error(`erro inesperado na run ${run.id}:`, error)).finally(() => {
361
+ this.activeRuns.delete(promise);
362
+ if (this.state === "started") this.schedule(0);
363
+ });
364
+ this.activeRuns.add(promise);
365
+ }
366
+ async executeRun(run) {
367
+ const reporter = new RunReporter(this.api, run.id, run.task, this.logger, this.logFlushIntervalMs);
368
+ this.reporters.add(reporter);
369
+ try {
370
+ const handler = this.registry.get(run.task);
371
+ if (!handler) {
372
+ reporter.reportFailure({
373
+ type: "HandlerNotFound",
374
+ message: `tarefa n\xE3o registrada no agente: ${run.task}`
375
+ });
376
+ await reporter.finalize();
377
+ return;
378
+ }
379
+ const ok = await reporter.reportRunning();
380
+ if (!ok) {
381
+ this.logger.debug(`run ${run.id} cancelada antes de iniciar \u2014 handler n\xE3o executado`);
382
+ return;
383
+ }
384
+ this.logger.debug(`executando run ${run.id} (${run.task})`);
385
+ try {
386
+ const result = await this.invokeWithTimeout(handler, run, reporter);
387
+ reporter.reportSuccess(result);
388
+ this.logger.debug(`run ${run.id} conclu\xEDda`);
389
+ } catch (error) {
390
+ reporter.reportFailure(toRunError(error));
391
+ this.logger.debug(`run ${run.id} falhou: ${String(error)}`);
392
+ }
393
+ await reporter.finalize();
394
+ } finally {
395
+ this.reporters.delete(reporter);
396
+ }
397
+ }
398
+ async invokeWithTimeout(handler, run, reporter) {
399
+ const invocation = Promise.resolve().then(() => handler(run.params, reporter.context));
400
+ if (!this.taskTimeoutMs || this.taskTimeoutMs <= 0) return invocation;
401
+ const timeoutMs = this.taskTimeoutMs;
402
+ const timeout = sleep(timeoutMs).then(() => TIMEOUT_SENTINEL);
403
+ const outcome = await Promise.race([invocation, timeout]);
404
+ if (outcome !== TIMEOUT_SENTINEL) return outcome;
405
+ reporter.abort();
406
+ invocation.then(() => this.logger.warn(`run ${run.id} terminou depois do timeout de ${timeoutMs}ms (resultado descartado)`)).catch(() => this.logger.warn(`run ${run.id} falhou depois do timeout de ${timeoutMs}ms (erro descartado)`));
407
+ throw Object.assign(new Error(`timeout ap\xF3s ${timeoutMs}ms (o handler pode continuar em background)`), {
408
+ name: "TaskTimeout"
409
+ });
410
+ }
411
+ };
412
+ var TIMEOUT_SENTINEL = /* @__PURE__ */ Symbol("task-timeout");
413
+ var MAX_ERROR_MESSAGE_CHARS = 1e4;
414
+ var MAX_ERROR_STACK_CHARS = 2e4;
415
+ function truncate(value, max) {
416
+ return value.length > max ? `${value.slice(0, max)}\u2026 [truncado]` : value;
417
+ }
418
+ function toRunError(error) {
419
+ if (error instanceof Error) {
420
+ return {
421
+ type: error.name,
422
+ message: truncate(error.message, MAX_ERROR_MESSAGE_CHARS),
423
+ stack: error.stack ? truncate(error.stack, MAX_ERROR_STACK_CHARS) : void 0
424
+ };
425
+ }
426
+ return { message: truncate(String(error), MAX_ERROR_MESSAGE_CHARS) };
427
+ }
428
+
429
+ // src/manager/core.ts
430
+ var agent;
431
+ var registry = /* @__PURE__ */ new Map();
432
+ function init(options) {
433
+ if (agent) {
434
+ agent.logger.warn("init() chamado mais de uma vez \u2014 substituindo o agente anterior");
435
+ void agent.stop(0);
436
+ }
437
+ agent = new ManagerAgent(options, registry);
438
+ }
439
+ function register(task, handler) {
440
+ if (registry.has(task)) {
441
+ agent?.logger.warn(`register('${task}') sobrescreveu um handler existente`);
442
+ }
443
+ registry.set(task, handler);
444
+ }
445
+ function start() {
446
+ if (!agent) {
447
+ throw new Error("[hobots-manager] init() precisa ser chamado antes de start()");
448
+ }
449
+ agent.start();
450
+ }
451
+ function stop(timeoutMs) {
452
+ return agent ? agent.stop(timeoutMs) : Promise.resolve(true);
453
+ }
454
+ export {
455
+ ManagerAgent,
456
+ ManagerApi,
457
+ ManagerApiError,
458
+ init,
459
+ parseManagerDsn,
460
+ register,
461
+ start,
462
+ stop
463
+ };
464
+ //# sourceMappingURL=manager.js.map