hobots 0.3.0 → 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.
- package/LICENSE +21 -0
- package/README.md +346 -0
- package/dist/chunk-FW32OR7V.js +75 -0
- package/dist/chunk-FW32OR7V.js.map +1 -0
- package/dist/index.cjs +764 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +187 -0
- package/dist/index.d.ts +187 -0
- package/dist/index.js +691 -0
- package/dist/index.js.map +1 -0
- package/dist/manager/protocol.cjs +19 -0
- package/dist/manager/protocol.cjs.map +1 -0
- package/dist/manager/protocol.d.cts +72 -0
- package/dist/manager/protocol.d.ts +72 -0
- package/dist/manager/protocol.js +1 -0
- package/dist/manager/protocol.js.map +1 -0
- package/dist/manager.cjs +519 -0
- package/dist/manager.cjs.map +1 -0
- package/dist/manager.d.cts +118 -0
- package/dist/manager.d.ts +118 -0
- package/dist/manager.js +464 -0
- package/dist/manager.js.map +1 -0
- package/dist/protocol.cjs +19 -0
- package/dist/protocol.cjs.map +1 -0
- package/dist/protocol.d.cts +126 -0
- package/dist/protocol.d.ts +126 -0
- package/dist/protocol.js +1 -0
- package/dist/protocol.js.map +1 -0
- package/dist/utils-LReLqjzN.d.cts +7 -0
- package/dist/utils-LReLqjzN.d.ts +7 -0
- package/docs/README.md +39 -0
- package/docs/api-reference.md +140 -0
- package/docs/getting-started.md +88 -0
- package/docs/manager.md +90 -0
- package/docs/monitor.md +136 -0
- package/docs/protocol.md +87 -0
- package/package.json +50 -2
- package/auth/index.d.ts +0 -10
- package/csv/index.d.ts +0 -9
- package/datasets/index.d.ts +0 -2
- package/db/drizzle.config.d.ts +0 -8
- package/db/index.d.ts +0 -9
- package/db/migrate.d.ts +0 -3
- package/db/schema.d.ts +0 -215
- package/db/storages/base.d.ts +0 -23
- package/db/storages/index.d.ts +0 -3
- package/db/storages/job.d.ts +0 -233
- package/exceptions/break.d.ts +0 -6
- package/exceptions/ignore.d.ts +0 -6
- package/exceptions/index.d.ts +0 -4
- package/exceptions/warning.d.ts +0 -6
- package/foreach/foreach.d.ts +0 -7
- package/foreach/index.d.ts +0 -5
- package/foreach/item.d.ts +0 -13
- package/hobots/samples/bot.d.ts +0 -2
- package/hobots/samples/index.d.ts +0 -2
- package/hobots/src/auth/index.d.ts +0 -10
- package/hobots/src/csv/index.d.ts +0 -9
- package/hobots/src/datasets/index.d.ts +0 -2
- package/hobots/src/db/drizzle.config.d.ts +0 -8
- package/hobots/src/db/index.d.ts +0 -9
- package/hobots/src/db/migrate.d.ts +0 -3
- package/hobots/src/db/schema.d.ts +0 -215
- package/hobots/src/db/storages/base.d.ts +0 -23
- package/hobots/src/db/storages/index.d.ts +0 -3
- package/hobots/src/db/storages/job.d.ts +0 -233
- package/hobots/src/exceptions/break.d.ts +0 -6
- package/hobots/src/exceptions/ignore.d.ts +0 -6
- package/hobots/src/exceptions/index.d.ts +0 -4
- package/hobots/src/exceptions/warning.d.ts +0 -6
- package/hobots/src/foreach/foreach.d.ts +0 -7
- package/hobots/src/foreach/index.d.ts +0 -5
- package/hobots/src/foreach/item.d.ts +0 -13
- package/hobots/src/index.d.ts +0 -9
- package/hobots/src/reports/data-report.d.ts +0 -9
- package/hobots/src/reports/index.d.ts +0 -3
- package/hobots/src/reports/job-report.d.ts +0 -22
- package/hobots/src/setup.d.ts +0 -20
- package/hobots/src/sheets/index.d.ts +0 -13
- package/hobots/src/types/hobots-types.d.ts +0 -20
- package/hobots/src/utils/date.d.ts +0 -117
- package/hobots/src/utils/dist.d.ts +0 -8
- package/hobots/src/utils/garbage.d.ts +0 -2
- package/hobots/src/utils/index.d.ts +0 -10
- package/hobots/src/utils/logger.d.ts +0 -21
- package/hobots/src/utils/manifest.d.ts +0 -2
- package/hobots/src/utils/path.d.ts +0 -13
- package/hobots/src/utils/publish.d.ts +0 -2
- package/hobots/src/utils/retry.d.ts +0 -5
- package/hobots/src/utils/text.d.ts +0 -74
- package/hobots/src/utils/timer.d.ts +0 -3
- package/index.d.ts +0 -9
- package/index.js +0 -131866
- package/index.js.map +0 -1
- package/reports/data-report.d.ts +0 -9
- package/reports/index.d.ts +0 -3
- package/reports/job-report.d.ts +0 -22
- package/setup.d.ts +0 -20
- package/sheets/index.d.ts +0 -13
- package/types/hobots-types.d.ts +0 -20
- package/utils/date.d.ts +0 -117
- package/utils/dist.d.ts +0 -8
- package/utils/garbage.d.ts +0 -2
- package/utils/index.d.ts +0 -10
- package/utils/logger.d.ts +0 -21
- package/utils/manifest.d.ts +0 -2
- package/utils/path.d.ts +0 -13
- package/utils/publish.d.ts +0 -2
- package/utils/retry.d.ts +0 -5
- package/utils/src/path.d.ts +0 -14
- package/utils/text.d.ts +0 -74
- package/utils/timer.d.ts +0 -3
package/dist/manager.cjs
ADDED
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/manager.ts
|
|
21
|
+
var manager_exports = {};
|
|
22
|
+
__export(manager_exports, {
|
|
23
|
+
ManagerAgent: () => ManagerAgent,
|
|
24
|
+
ManagerApi: () => ManagerApi,
|
|
25
|
+
ManagerApiError: () => ManagerApiError,
|
|
26
|
+
init: () => init,
|
|
27
|
+
parseManagerDsn: () => parseManagerDsn,
|
|
28
|
+
register: () => register,
|
|
29
|
+
start: () => start,
|
|
30
|
+
stop: () => stop
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(manager_exports);
|
|
33
|
+
|
|
34
|
+
// src/utils.ts
|
|
35
|
+
var SDK_VERSION = "1.0.0";
|
|
36
|
+
function isNode() {
|
|
37
|
+
return typeof process !== "undefined" && typeof process.versions?.node === "string";
|
|
38
|
+
}
|
|
39
|
+
function nowISO() {
|
|
40
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
41
|
+
}
|
|
42
|
+
function unrefTimer(timer) {
|
|
43
|
+
timer?.unref?.();
|
|
44
|
+
}
|
|
45
|
+
function sleep(ms) {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
const timer = setTimeout(resolve, ms);
|
|
48
|
+
unrefTimer(timer);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
var PREFIX = "[hobots-monitor]";
|
|
52
|
+
function makeLogger(enabled, prefix = PREFIX) {
|
|
53
|
+
return {
|
|
54
|
+
debug: (...args) => enabled && console.debug(prefix, ...args),
|
|
55
|
+
warn: (...args) => enabled && console.warn(prefix, ...args),
|
|
56
|
+
error: (...args) => console.error(prefix, ...args)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/manager/api.ts
|
|
61
|
+
var RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
|
|
62
|
+
var ManagerApiError = class extends Error {
|
|
63
|
+
constructor(message, status) {
|
|
64
|
+
super(message);
|
|
65
|
+
this.status = status;
|
|
66
|
+
this.name = "ManagerApiError";
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
var ManagerApi = class {
|
|
70
|
+
baseUrl;
|
|
71
|
+
key;
|
|
72
|
+
logger;
|
|
73
|
+
constructor(options) {
|
|
74
|
+
this.baseUrl = options.baseUrl;
|
|
75
|
+
this.key = options.key;
|
|
76
|
+
this.logger = options.logger;
|
|
77
|
+
}
|
|
78
|
+
headers() {
|
|
79
|
+
return {
|
|
80
|
+
"content-type": "application/json",
|
|
81
|
+
authorization: `Bearer ${this.key}`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** Um request só — o loop de polling é quem decide o backoff entre tentativas. */
|
|
85
|
+
async poll(request, signal) {
|
|
86
|
+
const response = await fetch(`${this.baseUrl}/poll`, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: this.headers(),
|
|
89
|
+
body: JSON.stringify(request),
|
|
90
|
+
signal
|
|
91
|
+
});
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
throw new ManagerApiError(`poll falhou (HTTP ${response.status})`, response.status);
|
|
94
|
+
}
|
|
95
|
+
return await response.json();
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* PATCH de status/logs com retry em falha de rede/5xx/429.
|
|
99
|
+
* 4xx é definitivo e vira ManagerApiError imediato (409 = run cancelada/transição inválida).
|
|
100
|
+
*/
|
|
101
|
+
async updateRun(runId, update) {
|
|
102
|
+
for (let attempt = 0; ; attempt++) {
|
|
103
|
+
let retriable;
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
|
|
106
|
+
method: "PATCH",
|
|
107
|
+
headers: this.headers(),
|
|
108
|
+
body: JSON.stringify(update)
|
|
109
|
+
});
|
|
110
|
+
if (response.ok) return;
|
|
111
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
112
|
+
throw new ManagerApiError(`update rejeitado (HTTP ${response.status})`, response.status);
|
|
113
|
+
}
|
|
114
|
+
retriable = new Error(`HTTP ${response.status}`);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
if (error instanceof ManagerApiError) throw error;
|
|
117
|
+
retriable = error;
|
|
118
|
+
}
|
|
119
|
+
const delay = RETRY_DELAYS_MS[attempt];
|
|
120
|
+
if (delay === void 0) {
|
|
121
|
+
throw new Error(`update da run ${runId} descartado ap\xF3s retries: ${String(retriable)}`);
|
|
122
|
+
}
|
|
123
|
+
this.logger.warn(`falha no update da run ${runId} (${String(retriable)}), retry em ${delay}ms`);
|
|
124
|
+
await sleep(delay);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// src/manager/context.ts
|
|
130
|
+
var DEFAULT_LOG_FLUSH_INTERVAL_MS = 2e3;
|
|
131
|
+
var MAX_LOG_MESSAGE_CHARS = 1e4;
|
|
132
|
+
var MAX_LOGS_PER_RUN = 500;
|
|
133
|
+
var MAX_LOGS_PER_PATCH = 100;
|
|
134
|
+
var FLUSH_AT_BUFFERED = 20;
|
|
135
|
+
var MAX_RESULT_CHARS = 1e5;
|
|
136
|
+
var RunReporter = class {
|
|
137
|
+
constructor(api, runId, task, logger, flushIntervalMs) {
|
|
138
|
+
this.api = api;
|
|
139
|
+
this.runId = runId;
|
|
140
|
+
this.logger = logger;
|
|
141
|
+
this.flushIntervalMs = flushIntervalMs ?? DEFAULT_LOG_FLUSH_INTERVAL_MS;
|
|
142
|
+
this.context = {
|
|
143
|
+
runId,
|
|
144
|
+
task,
|
|
145
|
+
log: (level, message) => this.log(level, message),
|
|
146
|
+
signal: this.abortController.signal
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
context;
|
|
150
|
+
buffer = [];
|
|
151
|
+
loggedCount = 0;
|
|
152
|
+
droppedCount = 0;
|
|
153
|
+
dead = false;
|
|
154
|
+
chain = Promise.resolve();
|
|
155
|
+
timer;
|
|
156
|
+
abortController = new AbortController();
|
|
157
|
+
flushIntervalMs;
|
|
158
|
+
get isDead() {
|
|
159
|
+
return this.dead;
|
|
160
|
+
}
|
|
161
|
+
/** Aborta o ctx.signal sem matar o reporter (timeout/shutdown ainda reportam o terminal). */
|
|
162
|
+
abort() {
|
|
163
|
+
if (!this.abortController.signal.aborted) this.abortController.abort();
|
|
164
|
+
}
|
|
165
|
+
log(level, message) {
|
|
166
|
+
if (this.dead) return;
|
|
167
|
+
if (this.loggedCount >= MAX_LOGS_PER_RUN) {
|
|
168
|
+
this.droppedCount++;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this.loggedCount++;
|
|
172
|
+
const truncated = message.length > MAX_LOG_MESSAGE_CHARS ? `${message.slice(0, MAX_LOG_MESSAGE_CHARS)}\u2026 [truncado]` : message;
|
|
173
|
+
this.buffer.push({ ts: nowISO(), level, message: truncated });
|
|
174
|
+
if (this.buffer.length >= FLUSH_AT_BUFFERED) {
|
|
175
|
+
this.flushLogs();
|
|
176
|
+
} else {
|
|
177
|
+
this.scheduleFlush();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
scheduleFlush() {
|
|
181
|
+
if (this.timer) return;
|
|
182
|
+
this.timer = setTimeout(() => {
|
|
183
|
+
this.timer = void 0;
|
|
184
|
+
this.flushLogs();
|
|
185
|
+
}, this.flushIntervalMs);
|
|
186
|
+
unrefTimer(this.timer);
|
|
187
|
+
}
|
|
188
|
+
clearFlushTimer() {
|
|
189
|
+
if (this.timer) {
|
|
190
|
+
clearTimeout(this.timer);
|
|
191
|
+
this.timer = void 0;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
takeLogs() {
|
|
195
|
+
const logs = this.buffer.splice(0, this.buffer.length);
|
|
196
|
+
if (this.droppedCount > 0) {
|
|
197
|
+
logs.push({
|
|
198
|
+
ts: nowISO(),
|
|
199
|
+
level: "warning",
|
|
200
|
+
message: `${this.droppedCount} logs descartados (limite de ${MAX_LOGS_PER_RUN} por execu\xE7\xE3o)`
|
|
201
|
+
});
|
|
202
|
+
this.droppedCount = 0;
|
|
203
|
+
}
|
|
204
|
+
return logs;
|
|
205
|
+
}
|
|
206
|
+
/** Flush incremental: envia o buffer como status 'running' (em chunks). */
|
|
207
|
+
flushLogs() {
|
|
208
|
+
this.clearFlushTimer();
|
|
209
|
+
if (this.dead || this.buffer.length === 0) return;
|
|
210
|
+
const logs = this.buffer.splice(0, this.buffer.length);
|
|
211
|
+
for (let i = 0; i < logs.length; i += MAX_LOGS_PER_PATCH) {
|
|
212
|
+
this.enqueue({ status: "running", logs: logs.slice(i, i + MAX_LOGS_PER_PATCH) });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/** Primeiro PATCH (claimed → running). Retorna false se a run já morreu (cancelada). */
|
|
216
|
+
async reportRunning() {
|
|
217
|
+
this.enqueue({ status: "running" });
|
|
218
|
+
await this.chain;
|
|
219
|
+
return !this.dead;
|
|
220
|
+
}
|
|
221
|
+
reportSuccess(result) {
|
|
222
|
+
this.clearFlushTimer();
|
|
223
|
+
this.enqueue({ status: "succeeded", result: serializeResult(result, this.logger), logs: this.takeLogs() });
|
|
224
|
+
}
|
|
225
|
+
reportFailure(error) {
|
|
226
|
+
this.clearFlushTimer();
|
|
227
|
+
this.enqueue({ status: "failed", error, logs: this.takeLogs() });
|
|
228
|
+
}
|
|
229
|
+
/** Aguarda a fila de PATCHes esvaziar (chame depois do terminal). */
|
|
230
|
+
finalize() {
|
|
231
|
+
return this.chain;
|
|
232
|
+
}
|
|
233
|
+
enqueue(update) {
|
|
234
|
+
if (this.dead) return;
|
|
235
|
+
this.chain = this.chain.then(async () => {
|
|
236
|
+
if (this.dead) return;
|
|
237
|
+
const payload = update.logs && update.logs.length === 0 ? { ...update, logs: void 0 } : update;
|
|
238
|
+
try {
|
|
239
|
+
await this.api.updateRun(this.runId, payload);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
if (error instanceof ManagerApiError && (error.status === 409 || error.status === 404)) {
|
|
242
|
+
this.dead = true;
|
|
243
|
+
this.abort();
|
|
244
|
+
this.buffer = [];
|
|
245
|
+
this.logger.warn(`run ${this.runId} cancelada/removida no servidor \u2014 reporte suprimido`);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
this.logger.error(`update da run ${this.runId} perdido:`, error);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
function serializeResult(result, logger) {
|
|
254
|
+
if (result === void 0) return void 0;
|
|
255
|
+
let json;
|
|
256
|
+
try {
|
|
257
|
+
json = JSON.stringify(result);
|
|
258
|
+
} catch {
|
|
259
|
+
logger.warn("result n\xE3o serializ\xE1vel (circular/BigInt?) \u2014 descartado");
|
|
260
|
+
return { __truncated: true, message: "resultado n\xE3o serializ\xE1vel e foi descartado pelo SDK" };
|
|
261
|
+
}
|
|
262
|
+
if (json === void 0) return void 0;
|
|
263
|
+
if (json.length > MAX_RESULT_CHARS) {
|
|
264
|
+
logger.warn(`result excede ${MAX_RESULT_CHARS} chars \u2014 descartado`);
|
|
265
|
+
return { __truncated: true, message: "resultado excede 100KB e foi descartado pelo SDK" };
|
|
266
|
+
}
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// src/dsn.ts
|
|
271
|
+
function parseDsnUrl(dsn, prefix) {
|
|
272
|
+
let url;
|
|
273
|
+
try {
|
|
274
|
+
url = new URL(dsn);
|
|
275
|
+
} catch {
|
|
276
|
+
throw new Error(`${prefix} DSN inv\xE1lido: "${dsn}"`);
|
|
277
|
+
}
|
|
278
|
+
const key = url.username;
|
|
279
|
+
const path = url.pathname.replace(/^\/+/, "");
|
|
280
|
+
if (!key || !path) {
|
|
281
|
+
throw new Error(`${prefix} DSN inv\xE1lido: "${dsn}" \u2014 esperado https://<key>@host/<projeto>`);
|
|
282
|
+
}
|
|
283
|
+
return { key, path, origin: `${url.protocol}//${url.host}` };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/manager/dsn.ts
|
|
287
|
+
function parseManagerDsn(dsn) {
|
|
288
|
+
const { key, path, origin } = parseDsnUrl(dsn, "[hobots-manager]");
|
|
289
|
+
return {
|
|
290
|
+
key,
|
|
291
|
+
projectSlug: path,
|
|
292
|
+
origin,
|
|
293
|
+
baseUrl: `${origin}/api/manager`
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/manager/agent.ts
|
|
298
|
+
var DEFAULT_POLL_INTERVAL_MS = 5e3;
|
|
299
|
+
var MIN_POLL_INTERVAL_MS = 1e3;
|
|
300
|
+
var MAX_CONCURRENCY = 5;
|
|
301
|
+
var POLL_RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
|
|
302
|
+
var AUTH_FAILURE_INTERVAL_MS = 3e4;
|
|
303
|
+
function defaultAgentName() {
|
|
304
|
+
if (!isNode()) return void 0;
|
|
305
|
+
return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
|
|
306
|
+
}
|
|
307
|
+
var ManagerAgent = class {
|
|
308
|
+
constructor(options, registry2) {
|
|
309
|
+
this.options = options;
|
|
310
|
+
this.registry = registry2;
|
|
311
|
+
this.logger = makeLogger(options.debug ?? false, "[hobots-manager]");
|
|
312
|
+
const dsn = parseManagerDsn(options.dsn);
|
|
313
|
+
this.api = new ManagerApi({ baseUrl: dsn.baseUrl, key: dsn.key, logger: this.logger });
|
|
314
|
+
this.pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
|
|
315
|
+
this.concurrency = Math.min(MAX_CONCURRENCY, Math.max(1, options.concurrency ?? 1));
|
|
316
|
+
this.agentName = options.agentName ?? defaultAgentName();
|
|
317
|
+
this.taskTimeoutMs = options.taskTimeoutMs;
|
|
318
|
+
this.logFlushIntervalMs = options.logFlushIntervalMs;
|
|
319
|
+
this.logger.debug("agente configurado", { baseUrl: dsn.baseUrl, projectSlug: dsn.projectSlug });
|
|
320
|
+
}
|
|
321
|
+
logger;
|
|
322
|
+
api;
|
|
323
|
+
pollIntervalMs;
|
|
324
|
+
concurrency;
|
|
325
|
+
agentName;
|
|
326
|
+
taskTimeoutMs;
|
|
327
|
+
logFlushIntervalMs;
|
|
328
|
+
state = "idle";
|
|
329
|
+
timer;
|
|
330
|
+
pollAbort;
|
|
331
|
+
consecutiveFailures = 0;
|
|
332
|
+
consecutiveAuthFailures = 0;
|
|
333
|
+
activeRuns = /* @__PURE__ */ new Set();
|
|
334
|
+
reporters = /* @__PURE__ */ new Set();
|
|
335
|
+
get activeCount() {
|
|
336
|
+
return this.activeRuns.size;
|
|
337
|
+
}
|
|
338
|
+
start() {
|
|
339
|
+
if (this.state === "started") return;
|
|
340
|
+
if (this.state === "stopping") {
|
|
341
|
+
throw new Error("[hobots-manager] start() chamado durante stop() \u2014 aguarde o stop terminar");
|
|
342
|
+
}
|
|
343
|
+
if (this.registry.size === 0) {
|
|
344
|
+
throw new Error("[hobots-manager] nenhuma task registrada \u2014 chame register() antes de start()");
|
|
345
|
+
}
|
|
346
|
+
this.state = "started";
|
|
347
|
+
this.logger.debug(`polling iniciado (tasks: ${[...this.registry.keys()].join(", ")})`);
|
|
348
|
+
this.schedule(0);
|
|
349
|
+
}
|
|
350
|
+
/** Para o polling e aguarda as runs ativas (até timeoutMs). Retorna true se tudo terminou a tempo. */
|
|
351
|
+
async stop(timeoutMs = 3e4) {
|
|
352
|
+
if (this.state === "idle") return true;
|
|
353
|
+
this.state = "stopping";
|
|
354
|
+
if (this.timer) {
|
|
355
|
+
clearTimeout(this.timer);
|
|
356
|
+
this.timer = void 0;
|
|
357
|
+
}
|
|
358
|
+
this.pollAbort?.abort();
|
|
359
|
+
const all = Promise.allSettled([...this.activeRuns]).then(() => true);
|
|
360
|
+
const finished = timeoutMs > 0 ? await Promise.race([all, sleep(timeoutMs).then(() => false)]) : false;
|
|
361
|
+
if (!finished && this.activeRuns.size > 0) {
|
|
362
|
+
this.logger.warn(`${this.activeRuns.size} run(s) ainda em execu\xE7\xE3o ap\xF3s ${timeoutMs}ms \u2014 abandonando a espera`);
|
|
363
|
+
for (const reporter of this.reporters) reporter.abort();
|
|
364
|
+
}
|
|
365
|
+
this.state = "idle";
|
|
366
|
+
return finished;
|
|
367
|
+
}
|
|
368
|
+
/** Timer intencionalmente NÃO unref'd: o agente é a razão do processo existir. */
|
|
369
|
+
schedule(delayMs) {
|
|
370
|
+
if (this.state !== "started" || this.timer) return;
|
|
371
|
+
this.timer = setTimeout(() => {
|
|
372
|
+
this.timer = void 0;
|
|
373
|
+
void this.tick();
|
|
374
|
+
}, delayMs);
|
|
375
|
+
}
|
|
376
|
+
async tick() {
|
|
377
|
+
if (this.state !== "started") return;
|
|
378
|
+
if (this.activeRuns.size >= this.concurrency) {
|
|
379
|
+
this.schedule(this.pollIntervalMs);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
this.pollAbort = new AbortController();
|
|
383
|
+
try {
|
|
384
|
+
const response = await this.api.poll(
|
|
385
|
+
{
|
|
386
|
+
agent: {
|
|
387
|
+
name: this.agentName,
|
|
388
|
+
version: SDK_VERSION,
|
|
389
|
+
host: defaultAgentName(),
|
|
390
|
+
tasks: [...this.registry.keys()]
|
|
391
|
+
},
|
|
392
|
+
max: Math.min(MAX_CONCURRENCY, this.concurrency - this.activeRuns.size),
|
|
393
|
+
tasks: [...this.registry.keys()]
|
|
394
|
+
},
|
|
395
|
+
this.pollAbort.signal
|
|
396
|
+
);
|
|
397
|
+
this.consecutiveFailures = 0;
|
|
398
|
+
this.consecutiveAuthFailures = 0;
|
|
399
|
+
for (const run of response.runs) {
|
|
400
|
+
this.dispatch(run);
|
|
401
|
+
}
|
|
402
|
+
this.schedule(response.runs.length > 0 ? 0 : this.pollIntervalMs);
|
|
403
|
+
} catch (error) {
|
|
404
|
+
if (this.state !== "started") return;
|
|
405
|
+
this.schedule(this.delayAfterPollError(error));
|
|
406
|
+
} finally {
|
|
407
|
+
this.pollAbort = void 0;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
delayAfterPollError(error) {
|
|
411
|
+
if (error instanceof ManagerApiError && error.status === 401) {
|
|
412
|
+
this.consecutiveAuthFailures++;
|
|
413
|
+
if (this.consecutiveAuthFailures === 1 || this.consecutiveAuthFailures % 10 === 0) {
|
|
414
|
+
this.logger.error("chave do agente rejeitada (401) \u2014 verifique a chave em /manager/agents");
|
|
415
|
+
}
|
|
416
|
+
return Math.max(this.pollIntervalMs, AUTH_FAILURE_INTERVAL_MS);
|
|
417
|
+
}
|
|
418
|
+
this.consecutiveFailures++;
|
|
419
|
+
const ladder = POLL_RETRY_DELAYS_MS[Math.min(this.consecutiveFailures - 1, POLL_RETRY_DELAYS_MS.length - 1)];
|
|
420
|
+
const delay = Math.min(Math.max(ladder, 0), Math.max(this.pollIntervalMs, 16e3));
|
|
421
|
+
this.logger.warn(`poll falhou (${String(error)}), pr\xF3xima tentativa em ${delay}ms`);
|
|
422
|
+
return delay;
|
|
423
|
+
}
|
|
424
|
+
dispatch(run) {
|
|
425
|
+
const promise = this.executeRun(run).catch((error) => this.logger.error(`erro inesperado na run ${run.id}:`, error)).finally(() => {
|
|
426
|
+
this.activeRuns.delete(promise);
|
|
427
|
+
if (this.state === "started") this.schedule(0);
|
|
428
|
+
});
|
|
429
|
+
this.activeRuns.add(promise);
|
|
430
|
+
}
|
|
431
|
+
async executeRun(run) {
|
|
432
|
+
const reporter = new RunReporter(this.api, run.id, run.task, this.logger, this.logFlushIntervalMs);
|
|
433
|
+
this.reporters.add(reporter);
|
|
434
|
+
try {
|
|
435
|
+
const handler = this.registry.get(run.task);
|
|
436
|
+
if (!handler) {
|
|
437
|
+
reporter.reportFailure({
|
|
438
|
+
type: "HandlerNotFound",
|
|
439
|
+
message: `tarefa n\xE3o registrada no agente: ${run.task}`
|
|
440
|
+
});
|
|
441
|
+
await reporter.finalize();
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const ok = await reporter.reportRunning();
|
|
445
|
+
if (!ok) {
|
|
446
|
+
this.logger.debug(`run ${run.id} cancelada antes de iniciar \u2014 handler n\xE3o executado`);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
this.logger.debug(`executando run ${run.id} (${run.task})`);
|
|
450
|
+
try {
|
|
451
|
+
const result = await this.invokeWithTimeout(handler, run, reporter);
|
|
452
|
+
reporter.reportSuccess(result);
|
|
453
|
+
this.logger.debug(`run ${run.id} conclu\xEDda`);
|
|
454
|
+
} catch (error) {
|
|
455
|
+
reporter.reportFailure(toRunError(error));
|
|
456
|
+
this.logger.debug(`run ${run.id} falhou: ${String(error)}`);
|
|
457
|
+
}
|
|
458
|
+
await reporter.finalize();
|
|
459
|
+
} finally {
|
|
460
|
+
this.reporters.delete(reporter);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
async invokeWithTimeout(handler, run, reporter) {
|
|
464
|
+
const invocation = Promise.resolve().then(() => handler(run.params, reporter.context));
|
|
465
|
+
if (!this.taskTimeoutMs || this.taskTimeoutMs <= 0) return invocation;
|
|
466
|
+
const timeoutMs = this.taskTimeoutMs;
|
|
467
|
+
const timeout = sleep(timeoutMs).then(() => TIMEOUT_SENTINEL);
|
|
468
|
+
const outcome = await Promise.race([invocation, timeout]);
|
|
469
|
+
if (outcome !== TIMEOUT_SENTINEL) return outcome;
|
|
470
|
+
reporter.abort();
|
|
471
|
+
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)`));
|
|
472
|
+
throw Object.assign(new Error(`timeout ap\xF3s ${timeoutMs}ms (o handler pode continuar em background)`), {
|
|
473
|
+
name: "TaskTimeout"
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
var TIMEOUT_SENTINEL = /* @__PURE__ */ Symbol("task-timeout");
|
|
478
|
+
var MAX_ERROR_MESSAGE_CHARS = 1e4;
|
|
479
|
+
var MAX_ERROR_STACK_CHARS = 2e4;
|
|
480
|
+
function truncate(value, max) {
|
|
481
|
+
return value.length > max ? `${value.slice(0, max)}\u2026 [truncado]` : value;
|
|
482
|
+
}
|
|
483
|
+
function toRunError(error) {
|
|
484
|
+
if (error instanceof Error) {
|
|
485
|
+
return {
|
|
486
|
+
type: error.name,
|
|
487
|
+
message: truncate(error.message, MAX_ERROR_MESSAGE_CHARS),
|
|
488
|
+
stack: error.stack ? truncate(error.stack, MAX_ERROR_STACK_CHARS) : void 0
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
return { message: truncate(String(error), MAX_ERROR_MESSAGE_CHARS) };
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// src/manager/core.ts
|
|
495
|
+
var agent;
|
|
496
|
+
var registry = /* @__PURE__ */ new Map();
|
|
497
|
+
function init(options) {
|
|
498
|
+
if (agent) {
|
|
499
|
+
agent.logger.warn("init() chamado mais de uma vez \u2014 substituindo o agente anterior");
|
|
500
|
+
void agent.stop(0);
|
|
501
|
+
}
|
|
502
|
+
agent = new ManagerAgent(options, registry);
|
|
503
|
+
}
|
|
504
|
+
function register(task, handler) {
|
|
505
|
+
if (registry.has(task)) {
|
|
506
|
+
agent?.logger.warn(`register('${task}') sobrescreveu um handler existente`);
|
|
507
|
+
}
|
|
508
|
+
registry.set(task, handler);
|
|
509
|
+
}
|
|
510
|
+
function start() {
|
|
511
|
+
if (!agent) {
|
|
512
|
+
throw new Error("[hobots-manager] init() precisa ser chamado antes de start()");
|
|
513
|
+
}
|
|
514
|
+
agent.start();
|
|
515
|
+
}
|
|
516
|
+
function stop(timeoutMs) {
|
|
517
|
+
return agent ? agent.stop(timeoutMs) : Promise.resolve(true);
|
|
518
|
+
}
|
|
519
|
+
//# sourceMappingURL=manager.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/manager.ts","../src/utils.ts","../src/manager/api.ts","../src/manager/context.ts","../src/dsn.ts","../src/manager/dsn.ts","../src/manager/agent.ts","../src/manager/core.ts"],"sourcesContent":["/**\n * Hobots Manager — execução remota de tarefas sob demanda.\n *\n * Capability independente do monitoramento: credencial própria (manager key),\n * singletons próprios e nenhum handler global instalado. Pode conviver com\n * `Monitor.init()` no mesmo processo.\n *\n * ```ts\n * import * as Manager from 'hobots/manager';\n *\n * Manager.init({ dsn: 'https://<managerKey>@monitor.hobots.app/robo-nfe' });\n * Manager.register('processar-nfe', async (params, ctx) => {\n * ctx.log('info', 'começando…');\n * return { processadas: 12 };\n * });\n * Manager.start();\n * ```\n */\n\nexport { init, register, start, stop } from './manager/core';\nexport { ManagerAgent } from './manager/agent';\nexport { ManagerApi, ManagerApiError } from './manager/api';\nexport { parseManagerDsn, type ParsedManagerDsn } from './manager/dsn';\nexport type { ManagerInitOptions, ManagerTaskHandler, RunContext } from './manager/types';\n\nexport type {\n ManagerRunStatus,\n ManagerRunUpdateStatus,\n RunLogLevel,\n RunLogEntry,\n ManagerAgentInfo,\n ManagerPollRequest,\n ManagerRunDescriptor,\n ManagerPollResponse,\n ManagerRunError,\n ManagerRunUpdateRequest,\n ManagerRunUpdateResponse,\n} from './manager/protocol';\n","export const SDK_NAME = 'hobots';\nexport const SDK_VERSION = '1.0.0';\n\nexport function isNode(): boolean {\n return typeof process !== 'undefined' && typeof process.versions?.node === 'string';\n}\n\n/** uuid v4 sem hífens (32 hex). Fallback para contextos sem crypto.randomUUID. */\nexport function uuid4(): string {\n const cryptoRef = (globalThis as { crypto?: Crypto }).crypto;\n if (cryptoRef && typeof cryptoRef.randomUUID === 'function') {\n return cryptoRef.randomUUID().replace(/-/g, '');\n }\n let out = '';\n for (let i = 0; i < 32; i++) {\n out += Math.floor(Math.random() * 16).toString(16);\n }\n return out;\n}\n\nexport function nowISO(): string {\n return new Date().toISOString();\n}\n\n/** Timers não devem segurar o processo Node aberto. */\nexport function unrefTimer(timer: unknown): void {\n (timer as { unref?: () => void } | undefined)?.unref?.();\n}\n\nexport function sleep(ms: number): Promise<void> {\n return new Promise(resolve => {\n const timer = setTimeout(resolve, ms);\n unrefTimer(timer);\n });\n}\n\nexport interface Logger {\n debug: (...args: unknown[]) => void;\n warn: (...args: unknown[]) => void;\n error: (...args: unknown[]) => void;\n}\n\nconst PREFIX = '[hobots-monitor]';\n\nexport function makeLogger(enabled: boolean, prefix: string = PREFIX): Logger {\n return {\n debug: (...args) => enabled && console.debug(prefix, ...args),\n warn: (...args) => enabled && console.warn(prefix, ...args),\n error: (...args) => console.error(prefix, ...args),\n };\n}\n","import type { ManagerPollRequest, ManagerPollResponse, ManagerRunUpdateRequest } from './protocol';\nimport { sleep, type Logger } from '../utils';\n\nconst RETRY_DELAYS_MS = [1_000, 4_000, 16_000];\n\nexport class ManagerApiError extends Error {\n constructor(\n message: string,\n readonly status: number\n ) {\n super(message);\n this.name = 'ManagerApiError';\n }\n}\n\nexport interface ManagerApiOptions {\n baseUrl: string;\n key: string;\n logger: Logger;\n}\n\n/** Cliente HTTP do agente — bearer auth, retry com backoff nos updates. */\nexport class ManagerApi {\n private readonly baseUrl: string;\n private readonly key: string;\n private readonly logger: Logger;\n\n constructor(options: ManagerApiOptions) {\n this.baseUrl = options.baseUrl;\n this.key = options.key;\n this.logger = options.logger;\n }\n\n private headers(): Record<string, string> {\n return {\n 'content-type': 'application/json',\n authorization: `Bearer ${this.key}`,\n };\n }\n\n /** Um request só — o loop de polling é quem decide o backoff entre tentativas. */\n async poll(request: ManagerPollRequest, signal?: AbortSignal): Promise<ManagerPollResponse> {\n const response = await fetch(`${this.baseUrl}/poll`, {\n method: 'POST',\n headers: this.headers(),\n body: JSON.stringify(request),\n signal,\n });\n if (!response.ok) {\n throw new ManagerApiError(`poll falhou (HTTP ${response.status})`, response.status);\n }\n return (await response.json()) as ManagerPollResponse;\n }\n\n /**\n * PATCH de status/logs com retry em falha de rede/5xx/429.\n * 4xx é definitivo e vira ManagerApiError imediato (409 = run cancelada/transição inválida).\n */\n async updateRun(runId: string, update: ManagerRunUpdateRequest): Promise<void> {\n for (let attempt = 0; ; attempt++) {\n let retriable: unknown;\n try {\n const response = await fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {\n method: 'PATCH',\n headers: this.headers(),\n body: JSON.stringify(update),\n });\n if (response.ok) return;\n if (response.status >= 400 && response.status < 500 && response.status !== 429) {\n throw new ManagerApiError(`update rejeitado (HTTP ${response.status})`, response.status);\n }\n retriable = new Error(`HTTP ${response.status}`);\n } catch (error) {\n if (error instanceof ManagerApiError) throw error;\n retriable = error;\n }\n\n const delay = RETRY_DELAYS_MS[attempt];\n if (delay === undefined) {\n throw new Error(`update da run ${runId} descartado após retries: ${String(retriable)}`);\n }\n this.logger.warn(`falha no update da run ${runId} (${String(retriable)}), retry em ${delay}ms`);\n await sleep(delay);\n }\n }\n}\n","import type { ManagerRunError, ManagerRunUpdateRequest, RunLogEntry, RunLogLevel } from './protocol';\nimport type { RunContext } from './types';\nimport { ManagerApi, ManagerApiError } from './api';\nimport { nowISO, unrefTimer, type Logger } from '../utils';\n\nconst DEFAULT_LOG_FLUSH_INTERVAL_MS = 2_000;\nconst MAX_LOG_MESSAGE_CHARS = 10_000;\nconst MAX_LOGS_PER_RUN = 500;\nconst MAX_LOGS_PER_PATCH = 100;\nconst FLUSH_AT_BUFFERED = 20;\nconst MAX_RESULT_CHARS = 100_000;\n\n/**\n * Reporter de uma run: bufferiza logs (flush incremental com throttle) e encadeia\n * todos os PATCHes numa fila única — a ordem running → logs → terminal é garantida.\n * Um 409/404 mata o reporter (run cancelada no app): o handler não pode ser morto,\n * mas todo reporte posterior é suprimido e ctx.signal dispara para abort cooperativo.\n */\nexport class RunReporter {\n readonly context: RunContext;\n\n private buffer: RunLogEntry[] = [];\n private loggedCount = 0;\n private droppedCount = 0;\n private dead = false;\n private chain: Promise<void> = Promise.resolve();\n private timer: ReturnType<typeof setTimeout> | undefined;\n private readonly abortController = new AbortController();\n private readonly flushIntervalMs: number;\n\n constructor(\n private readonly api: ManagerApi,\n private readonly runId: string,\n task: string,\n private readonly logger: Logger,\n flushIntervalMs?: number\n ) {\n this.flushIntervalMs = flushIntervalMs ?? DEFAULT_LOG_FLUSH_INTERVAL_MS;\n this.context = {\n runId,\n task,\n log: (level, message) => this.log(level, message),\n signal: this.abortController.signal,\n };\n }\n\n get isDead(): boolean {\n return this.dead;\n }\n\n /** Aborta o ctx.signal sem matar o reporter (timeout/shutdown ainda reportam o terminal). */\n abort(): void {\n if (!this.abortController.signal.aborted) this.abortController.abort();\n }\n\n private log(level: RunLogLevel, message: string): void {\n if (this.dead) return;\n if (this.loggedCount >= MAX_LOGS_PER_RUN) {\n this.droppedCount++;\n return;\n }\n this.loggedCount++;\n const truncated =\n message.length > MAX_LOG_MESSAGE_CHARS ? `${message.slice(0, MAX_LOG_MESSAGE_CHARS)}… [truncado]` : message;\n this.buffer.push({ ts: nowISO(), level, message: truncated });\n\n if (this.buffer.length >= FLUSH_AT_BUFFERED) {\n this.flushLogs();\n } else {\n this.scheduleFlush();\n }\n }\n\n private scheduleFlush(): void {\n if (this.timer) return;\n this.timer = setTimeout(() => {\n this.timer = undefined;\n this.flushLogs();\n }, this.flushIntervalMs);\n unrefTimer(this.timer);\n }\n\n private clearFlushTimer(): void {\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n }\n\n private takeLogs(): RunLogEntry[] {\n const logs = this.buffer.splice(0, this.buffer.length);\n if (this.droppedCount > 0) {\n logs.push({\n ts: nowISO(),\n level: 'warning',\n message: `${this.droppedCount} logs descartados (limite de ${MAX_LOGS_PER_RUN} por execução)`,\n });\n this.droppedCount = 0;\n }\n return logs;\n }\n\n /** Flush incremental: envia o buffer como status 'running' (em chunks). */\n private flushLogs(): void {\n this.clearFlushTimer();\n if (this.dead || this.buffer.length === 0) return;\n const logs = this.buffer.splice(0, this.buffer.length);\n for (let i = 0; i < logs.length; i += MAX_LOGS_PER_PATCH) {\n this.enqueue({ status: 'running', logs: logs.slice(i, i + MAX_LOGS_PER_PATCH) });\n }\n }\n\n /** Primeiro PATCH (claimed → running). Retorna false se a run já morreu (cancelada). */\n async reportRunning(): Promise<boolean> {\n this.enqueue({ status: 'running' });\n await this.chain;\n return !this.dead;\n }\n\n reportSuccess(result: unknown): void {\n this.clearFlushTimer();\n this.enqueue({ status: 'succeeded', result: serializeResult(result, this.logger), logs: this.takeLogs() });\n }\n\n reportFailure(error: ManagerRunError): void {\n this.clearFlushTimer();\n this.enqueue({ status: 'failed', error, logs: this.takeLogs() });\n }\n\n /** Aguarda a fila de PATCHes esvaziar (chame depois do terminal). */\n finalize(): Promise<void> {\n return this.chain;\n }\n\n private enqueue(update: ManagerRunUpdateRequest): void {\n if (this.dead) return;\n this.chain = this.chain.then(async () => {\n if (this.dead) return;\n const payload = update.logs && update.logs.length === 0 ? { ...update, logs: undefined } : update;\n try {\n await this.api.updateRun(this.runId, payload);\n } catch (error) {\n if (error instanceof ManagerApiError && (error.status === 409 || error.status === 404)) {\n this.dead = true;\n this.abort();\n this.buffer = [];\n this.logger.warn(`run ${this.runId} cancelada/removida no servidor — reporte suprimido`);\n return;\n }\n this.logger.error(`update da run ${this.runId} perdido:`, error);\n }\n });\n }\n}\n\n/** Serializa o result com cap de tamanho — um result gigante não pode falhar uma run bem-sucedida. */\nfunction serializeResult(result: unknown, logger: Logger): unknown {\n if (result === undefined) return undefined;\n let json: string;\n try {\n json = JSON.stringify(result);\n } catch {\n logger.warn('result não serializável (circular/BigInt?) — descartado');\n return { __truncated: true, message: 'resultado não serializável e foi descartado pelo SDK' };\n }\n if (json === undefined) return undefined;\n if (json.length > MAX_RESULT_CHARS) {\n logger.warn(`result excede ${MAX_RESULT_CHARS} chars — descartado`);\n return { __truncated: true, message: 'resultado excede 100KB e foi descartado pelo SDK' };\n }\n return result;\n}\n","export interface ParsedDsn {\n publicKey: string;\n projectId: string;\n /** URL de ingestão derivada: <origin>/api/ingest/<publicKey> */\n endpoint: string;\n}\n\ninterface ParsedDsnUrl {\n key: string;\n path: string;\n origin: string;\n}\n\n/** Parser baixo nível compartilhado entre Monitor e Manager (não exportado no barrel). */\nexport function parseDsnUrl(dsn: string, prefix: string): ParsedDsnUrl {\n let url: URL;\n try {\n url = new URL(dsn);\n } catch {\n throw new Error(`${prefix} DSN inválido: \"${dsn}\"`);\n }\n\n const key = url.username;\n const path = url.pathname.replace(/^\\/+/, '');\n if (!key || !path) {\n throw new Error(`${prefix} DSN inválido: \"${dsn}\" — esperado https://<key>@host/<projeto>`);\n }\n\n return { key, path, origin: `${url.protocol}//${url.host}` };\n}\n\n/** Formato: https://<publicKey>@host[:porta]/<projectId> */\nexport function parseDsn(dsn: string): ParsedDsn {\n const { key, path, origin } = parseDsnUrl(dsn, '[hobots-monitor]');\n return {\n publicKey: key,\n projectId: path,\n endpoint: `${origin}/api/ingest/${key}`,\n };\n}\n","import { parseDsnUrl } from '../dsn';\n\nexport interface ParsedManagerDsn {\n /** Chave secreta do agente (enviada como Authorization: Bearer, nunca na URL) */\n key: string;\n projectSlug: string;\n origin: string;\n /** Base derivada: <origin>/api/manager */\n baseUrl: string;\n}\n\n/** Formato: https://<managerKey>@host[:porta]/<projectSlug> */\nexport function parseManagerDsn(dsn: string): ParsedManagerDsn {\n const { key, path, origin } = parseDsnUrl(dsn, '[hobots-manager]');\n return {\n key,\n projectSlug: path,\n origin,\n baseUrl: `${origin}/api/manager`,\n };\n}\n","import type { ManagerRunDescriptor, ManagerRunError } from './protocol';\nimport type { ManagerInitOptions, ManagerTaskHandler } from './types';\nimport { ManagerApi, ManagerApiError } from './api';\nimport { RunReporter } from './context';\nimport { parseManagerDsn } from './dsn';\nimport { isNode, makeLogger, sleep, SDK_VERSION, type Logger } from '../utils';\n\nconst DEFAULT_POLL_INTERVAL_MS = 5_000;\nconst MIN_POLL_INTERVAL_MS = 1_000;\nconst MAX_CONCURRENCY = 5;\nconst POLL_RETRY_DELAYS_MS = [1_000, 4_000, 16_000];\nconst AUTH_FAILURE_INTERVAL_MS = 30_000;\n\ntype AgentState = 'idle' | 'started' | 'stopping';\n\nfunction defaultAgentName(): string | undefined {\n if (!isNode()) return undefined;\n return process.env.HOSTNAME || process.env.COMPUTERNAME || undefined;\n}\n\n/**\n * Loop de polling do Manager: busca runs pendentes, executa os handlers registrados\n * e reporta status/logs/result. Ao contrário do Monitor, o timer de polling NÃO é\n * unref'd — um agente iniciado mantém o processo vivo até stop().\n */\nexport class ManagerAgent {\n readonly logger: Logger;\n\n private readonly api: ManagerApi;\n private readonly pollIntervalMs: number;\n private readonly concurrency: number;\n private readonly agentName?: string;\n private readonly taskTimeoutMs?: number;\n private readonly logFlushIntervalMs?: number;\n\n private state: AgentState = 'idle';\n private timer: ReturnType<typeof setTimeout> | undefined;\n private pollAbort: AbortController | undefined;\n private consecutiveFailures = 0;\n private consecutiveAuthFailures = 0;\n private readonly activeRuns = new Set<Promise<void>>();\n private readonly reporters = new Set<RunReporter>();\n\n constructor(\n private readonly options: ManagerInitOptions,\n private readonly registry: Map<string, ManagerTaskHandler>\n ) {\n this.logger = makeLogger(options.debug ?? false, '[hobots-manager]');\n const dsn = parseManagerDsn(options.dsn);\n this.api = new ManagerApi({ baseUrl: dsn.baseUrl, key: dsn.key, logger: this.logger });\n this.pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);\n this.concurrency = Math.min(MAX_CONCURRENCY, Math.max(1, options.concurrency ?? 1));\n this.agentName = options.agentName ?? defaultAgentName();\n this.taskTimeoutMs = options.taskTimeoutMs;\n this.logFlushIntervalMs = options.logFlushIntervalMs;\n this.logger.debug('agente configurado', { baseUrl: dsn.baseUrl, projectSlug: dsn.projectSlug });\n }\n\n get activeCount(): number {\n return this.activeRuns.size;\n }\n\n start(): void {\n if (this.state === 'started') return;\n if (this.state === 'stopping') {\n throw new Error('[hobots-manager] start() chamado durante stop() — aguarde o stop terminar');\n }\n if (this.registry.size === 0) {\n throw new Error('[hobots-manager] nenhuma task registrada — chame register() antes de start()');\n }\n this.state = 'started';\n this.logger.debug(`polling iniciado (tasks: ${[...this.registry.keys()].join(', ')})`);\n this.schedule(0);\n }\n\n /** Para o polling e aguarda as runs ativas (até timeoutMs). Retorna true se tudo terminou a tempo. */\n async stop(timeoutMs = 30_000): Promise<boolean> {\n if (this.state === 'idle') return true;\n this.state = 'stopping';\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n this.pollAbort?.abort();\n\n const all = Promise.allSettled([...this.activeRuns]).then(() => true);\n const finished = timeoutMs > 0 ? await Promise.race([all, sleep(timeoutMs).then(() => false)]) : false;\n if (!finished && this.activeRuns.size > 0) {\n this.logger.warn(`${this.activeRuns.size} run(s) ainda em execução após ${timeoutMs}ms — abandonando a espera`);\n for (const reporter of this.reporters) reporter.abort();\n }\n this.state = 'idle';\n return finished;\n }\n\n /** Timer intencionalmente NÃO unref'd: o agente é a razão do processo existir. */\n private schedule(delayMs: number): void {\n if (this.state !== 'started' || this.timer) return;\n this.timer = setTimeout(() => {\n this.timer = undefined;\n void this.tick();\n }, delayMs);\n }\n\n private async tick(): Promise<void> {\n if (this.state !== 'started') return;\n if (this.activeRuns.size >= this.concurrency) {\n this.schedule(this.pollIntervalMs);\n return;\n }\n\n this.pollAbort = new AbortController();\n try {\n const response = await this.api.poll(\n {\n agent: {\n name: this.agentName,\n version: SDK_VERSION,\n host: defaultAgentName(),\n tasks: [...this.registry.keys()],\n },\n max: Math.min(MAX_CONCURRENCY, this.concurrency - this.activeRuns.size),\n tasks: [...this.registry.keys()],\n },\n this.pollAbort.signal\n );\n this.consecutiveFailures = 0;\n this.consecutiveAuthFailures = 0;\n\n for (const run of response.runs) {\n this.dispatch(run);\n }\n // com trabalho entregue, re-poll imediato (modo drain); sem trabalho, espera o intervalo\n this.schedule(response.runs.length > 0 ? 0 : this.pollIntervalMs);\n } catch (error) {\n if (this.state !== 'started') return; // stop() abortou o poll\n this.schedule(this.delayAfterPollError(error));\n } finally {\n this.pollAbort = undefined;\n }\n }\n\n private delayAfterPollError(error: unknown): number {\n if (error instanceof ManagerApiError && error.status === 401) {\n // chave desativada/rotacionada — continua tentando devagar (pode ser reativada)\n this.consecutiveAuthFailures++;\n if (this.consecutiveAuthFailures === 1 || this.consecutiveAuthFailures % 10 === 0) {\n this.logger.error('chave do agente rejeitada (401) — verifique a chave em /manager/agents');\n }\n return Math.max(this.pollIntervalMs, AUTH_FAILURE_INTERVAL_MS);\n }\n this.consecutiveFailures++;\n const ladder = POLL_RETRY_DELAYS_MS[Math.min(this.consecutiveFailures - 1, POLL_RETRY_DELAYS_MS.length - 1)]!;\n const delay = Math.min(Math.max(ladder, 0), Math.max(this.pollIntervalMs, 16_000));\n this.logger.warn(`poll falhou (${String(error)}), próxima tentativa em ${delay}ms`);\n return delay;\n }\n\n private dispatch(run: ManagerRunDescriptor): void {\n const promise = this.executeRun(run)\n .catch(error => this.logger.error(`erro inesperado na run ${run.id}:`, error))\n .finally(() => {\n this.activeRuns.delete(promise);\n if (this.state === 'started') this.schedule(0);\n });\n this.activeRuns.add(promise);\n }\n\n private async executeRun(run: ManagerRunDescriptor): Promise<void> {\n const reporter = new RunReporter(this.api, run.id, run.task, this.logger, this.logFlushIntervalMs);\n this.reporters.add(reporter);\n try {\n const handler = this.registry.get(run.task);\n if (!handler) {\n // claim veio para uma task sem handler (corrida rara) — falha direto, sem 'running'\n reporter.reportFailure({\n type: 'HandlerNotFound',\n message: `tarefa não registrada no agente: ${run.task}`,\n });\n await reporter.finalize();\n return;\n }\n\n const ok = await reporter.reportRunning();\n if (!ok) {\n this.logger.debug(`run ${run.id} cancelada antes de iniciar — handler não executado`);\n return;\n }\n\n this.logger.debug(`executando run ${run.id} (${run.task})`);\n try {\n const result = await this.invokeWithTimeout(handler, run, reporter);\n reporter.reportSuccess(result);\n this.logger.debug(`run ${run.id} concluída`);\n } catch (error) {\n reporter.reportFailure(toRunError(error));\n this.logger.debug(`run ${run.id} falhou: ${String(error)}`);\n }\n await reporter.finalize();\n } finally {\n this.reporters.delete(reporter);\n }\n }\n\n private async invokeWithTimeout(\n handler: ManagerTaskHandler,\n run: ManagerRunDescriptor,\n reporter: RunReporter\n ): Promise<unknown> {\n const invocation = Promise.resolve().then(() => handler(run.params, reporter.context));\n if (!this.taskTimeoutMs || this.taskTimeoutMs <= 0) return invocation;\n\n const timeoutMs = this.taskTimeoutMs;\n const timeout = sleep(timeoutMs).then(() => TIMEOUT_SENTINEL);\n const outcome = await Promise.race([invocation, timeout]);\n if (outcome !== TIMEOUT_SENTINEL) return outcome;\n\n // não dá para matar o código do usuário — reporta, aborta o signal e libera o slot\n reporter.abort();\n invocation\n .then(() => this.logger.warn(`run ${run.id} terminou depois do timeout de ${timeoutMs}ms (resultado descartado)`))\n .catch(() => this.logger.warn(`run ${run.id} falhou depois do timeout de ${timeoutMs}ms (erro descartado)`));\n throw Object.assign(new Error(`timeout após ${timeoutMs}ms (o handler pode continuar em background)`), {\n name: 'TaskTimeout',\n });\n }\n}\n\nconst TIMEOUT_SENTINEL = Symbol('task-timeout');\n\nconst MAX_ERROR_MESSAGE_CHARS = 10_000;\nconst MAX_ERROR_STACK_CHARS = 20_000;\n\nfunction truncate(value: string, max: number): string {\n return value.length > max ? `${value.slice(0, max)}… [truncado]` : value;\n}\n\nfunction toRunError(error: unknown): ManagerRunError {\n if (error instanceof Error) {\n return {\n type: error.name,\n message: truncate(error.message, MAX_ERROR_MESSAGE_CHARS),\n stack: error.stack ? truncate(error.stack, MAX_ERROR_STACK_CHARS) : undefined,\n };\n }\n return { message: truncate(String(error), MAX_ERROR_MESSAGE_CHARS) };\n}\n","import type { ManagerInitOptions, ManagerTaskHandler } from './types';\nimport { ManagerAgent } from './agent';\n\nlet agent: ManagerAgent | undefined;\nconst registry = new Map<string, ManagerTaskHandler>();\n\n/** Inicializa o agente Manager. Chame uma vez; depois register() e start(). */\nexport function init(options: ManagerInitOptions): void {\n if (agent) {\n agent.logger.warn('init() chamado mais de uma vez — substituindo o agente anterior');\n void agent.stop(0);\n }\n agent = new ManagerAgent(options, registry);\n}\n\n/** Registra o handler de uma task (o task_slug configurado no formulário do app). */\nexport function register<P = Record<string, unknown>, R = unknown>(task: string, handler: ManagerTaskHandler<P, R>): void {\n if (registry.has(task)) {\n agent?.logger.warn(`register('${task}') sobrescreveu um handler existente`);\n }\n registry.set(task, handler as ManagerTaskHandler);\n}\n\n/**\n * Inicia o polling. O timer NÃO é unref'd: o processo fica vivo aguardando\n * trabalho até stop() — o inverso deliberado do Monitor.\n */\nexport function start(): void {\n if (!agent) {\n throw new Error('[hobots-manager] init() precisa ser chamado antes de start()');\n }\n agent.start();\n}\n\n/** Para o polling e aguarda a run atual terminar (até timeoutMs). */\nexport function stop(timeoutMs?: number): Promise<boolean> {\n return agent ? agent.stop(timeoutMs) : Promise.resolve(true);\n}\n"],"mappings":"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","names":["registry"]}
|