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.
- 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/docs/manager.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Manager
|
|
2
|
+
|
|
3
|
+
Capability de **execução remota sob demanda**. O agente faz polling no app, executa os handlers registrados quando aparece trabalho e reporta status, logs incrementais e resultado. Entry próprio:
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
import * as Manager from 'hobots/manager';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Independente do Monitor: credencial própria (**manager key**), singletons próprios e nenhum handler global instalado. Pode conviver com `Monitor.init()` no mesmo processo.
|
|
10
|
+
|
|
11
|
+
## Ciclo de vida
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
Manager.init({ dsn: 'http://<managerKey>@localhost:3002/<projectSlug>', agentName: 'VM Cliente 01' });
|
|
15
|
+
|
|
16
|
+
Manager.register('processar-nfe', async (params, ctx) => {
|
|
17
|
+
ctx.log('info', `processando lote ${params.lote}…`);
|
|
18
|
+
if (ctx.signal.aborted) return; // cancelamento cooperativo
|
|
19
|
+
return { processadas: 12 }; // vira o resultado da execução no app
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
Manager.start(); // começa o polling e MANTÉM O PROCESSO VIVO
|
|
23
|
+
|
|
24
|
+
process.on('SIGINT', () => {
|
|
25
|
+
void Manager.stop(15_000).then(() => process.exit(0));
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- `init(options)` — cria o agente (singleton). Chamar de novo para o anterior e substitui.
|
|
30
|
+
- `register(task, handler)` — `task` é o `task_slug` configurado no formulário do app. Registre **antes** de `start()`.
|
|
31
|
+
- `start()` — começa o polling. Lança se `init()` não foi chamado ou se nenhuma task foi registrada.
|
|
32
|
+
- `stop(timeoutMs?)` — para o polling e aguarda as runs ativas (default 30s). Retorna `false` se estourou o timeout.
|
|
33
|
+
|
|
34
|
+
> **Atenção:** ao contrário do Monitor, `Manager.start()` **segura o processo vivo** aguardando trabalho até `Manager.stop()` — o agente é a razão do processo existir.
|
|
35
|
+
|
|
36
|
+
## O handler e o `RunContext`
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
type ManagerTaskHandler<P = Record<string, unknown>, R = unknown> =
|
|
40
|
+
(params: P, ctx: RunContext) => R | Promise<R>;
|
|
41
|
+
|
|
42
|
+
interface RunContext {
|
|
43
|
+
runId: string;
|
|
44
|
+
task: string;
|
|
45
|
+
log(level: 'debug' | 'info' | 'warning' | 'error', message: string): void;
|
|
46
|
+
signal: AbortSignal;
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- **`params`** — os dados do formulário configurado no app (o JSON validado).
|
|
51
|
+
- **`ctx.log(level, message)`** — logs enviados **incrementalmente** ao app (PATCHes com throttle), visíveis na run em tempo quase real.
|
|
52
|
+
- **`ctx.signal`** — `AbortSignal` que dispara quando a run é **cancelada no app**, em **timeout** ou no **shutdown**. Cancelamento é cooperativo (veja abaixo).
|
|
53
|
+
- **Retorno** — vira o resultado da execução exibido no app (serializado em JSON, cap de ~100KB; resultados maiores/circulares são descartados com um marcador `__truncated`).
|
|
54
|
+
- **Erro lançado** — a run é reportada como `failed` com tipo/mensagem/stack (mensagem cap 10k, stack 20k).
|
|
55
|
+
|
|
56
|
+
Você pode tipar params e retorno: `Manager.register<{ lote: number }, { processadas: number }>(...)`.
|
|
57
|
+
|
|
58
|
+
## Cancelamento cooperativo
|
|
59
|
+
|
|
60
|
+
Uma run cancelada no app **não mata** o seu handler — o SDK não interrompe código em execução. Ele apenas dispara `ctx.signal`. **Verifique `ctx.signal.aborted`** em loops longos e pontos de parada:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
Manager.register('processar-nfe', async (params, ctx) => {
|
|
64
|
+
for (const nota of params.notas) {
|
|
65
|
+
if (ctx.signal.aborted) {
|
|
66
|
+
ctx.log('warning', 'cancelamento detectado — parando');
|
|
67
|
+
return { canceladas: true };
|
|
68
|
+
}
|
|
69
|
+
await processar(nota);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Idempotência (at-least-once)
|
|
75
|
+
|
|
76
|
+
A entrega é **at-least-once**: se o agente morrer logo após capturar uma run (antes de reportar o resultado), ela é re-entregue quando o agente voltar. Escreva handlers **idempotentes** — processar a mesma run duas vezes não pode causar efeito duplicado (use uma chave de deduplicação sua, como o número da nota).
|
|
77
|
+
|
|
78
|
+
## Concorrência
|
|
79
|
+
|
|
80
|
+
`concurrency` (default 1, máx 5) controla quantas runs o agente executa em paralelo. Com 1, as runs são sequenciais. O agente só reivindica o que consegue rodar (`max = concurrency - runs ativas`) e entra em **drain mode**: enquanto vêm runs, re-pesquisa imediatamente; quando não vem nada, espera `pollIntervalMs`.
|
|
81
|
+
|
|
82
|
+
## Timeout por run
|
|
83
|
+
|
|
84
|
+
`taskTimeoutMs` é um timeout **client-side**. No estouro o SDK **não mata** o handler — ele dispara `ctx.signal`, libera o slot de concorrência e reporta a run como falha (`TaskTimeout`). O handler pode continuar rodando em background; respeite o `signal` para parar de verdade. Sem `taskTimeoutMs`, o backstop é o servidor.
|
|
85
|
+
|
|
86
|
+
Todas as opções em [Referência de API → ManagerInitOptions](./api-reference.md#managerinitoptions).
|
|
87
|
+
|
|
88
|
+
## Como o claim funciona (resumo)
|
|
89
|
+
|
|
90
|
+
O SDK é um cliente HTTP: ele faz `POST /api/manager/poll` e o **servidor** reivindica as runs pendentes de forma atômica com `FOR UPDATE SKIP LOCKED` — dois agentes nunca pegam a mesma run. Detalhes em [Protocolo & transporte](./protocol.md).
|
package/docs/monitor.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Monitor
|
|
2
|
+
|
|
3
|
+
Capability de telemetria: erros, performance, crons e uptime. Import raiz:
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
import * as Monitor from 'hobots';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Todas as funções abaixo são no-op seguros se `Monitor.init()` não foi chamado (com aviso no console). Assinaturas completas em [Referência de API](./api-reference.md).
|
|
10
|
+
|
|
11
|
+
## init
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
Monitor.init({
|
|
15
|
+
dsn: 'http://<publicKey>@localhost:3002/<projectId>',
|
|
16
|
+
environment: 'production',
|
|
17
|
+
release: 'robo-nfe@1.4.2',
|
|
18
|
+
serverName: 'vm-cliente-01',
|
|
19
|
+
tags: { squad: 'automacoes' },
|
|
20
|
+
heartbeat: true,
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Cria o cliente, instala handlers globais de erro (a menos que `captureUnhandled: false`) e inicia o heartbeat se configurado. Chamar de novo substitui o cliente. Todos os campos em [Referência de API → InitOptions](./api-reference.md#initoptions).
|
|
25
|
+
|
|
26
|
+
## Captura de erros e mensagens
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
// exceções
|
|
30
|
+
Monitor.captureException(error, {
|
|
31
|
+
tags: { cliente: 'acme' },
|
|
32
|
+
level: 'error',
|
|
33
|
+
fingerprint: ['sefaz-timeout'], // agrupa manualmente; sem isso o servidor agrupa pelo stack
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// mensagens sem exceção
|
|
37
|
+
Monitor.captureMessage('Lote processado com sucesso', 'info');
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Ambas retornam o `event_id` (ou `undefined` se não inicializado). O segundo argumento é um [`CaptureHint`](./api-reference.md#capturehint) (`level`, `tags`, `extra`, `fingerprint`).
|
|
41
|
+
|
|
42
|
+
Erros **não tratados** (`uncaughtException` / `unhandledRejection`) são capturados e flushados automaticamente antes do processo sair — desligue com `captureUnhandled: false`.
|
|
43
|
+
|
|
44
|
+
## Breadcrumbs
|
|
45
|
+
|
|
46
|
+
Trilha do que aconteceu antes do erro; enviada junto do próximo evento.
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
Monitor.addBreadcrumb({ category: 'nfe', message: 'Baixou XML da nota 123', level: 'info' });
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
O buffer é limitado por `maxBreadcrumbs` (default 100, mais antigos descartados).
|
|
53
|
+
|
|
54
|
+
## Escopo: tags, usuário, contexto, extras
|
|
55
|
+
|
|
56
|
+
Dados anexados a **todos** os eventos seguintes:
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
Monitor.setTag('regiao', 'nordeste');
|
|
60
|
+
Monitor.setTags({ squad: 'automacoes', cliente: 'acme' });
|
|
61
|
+
Monitor.setUser({ id: 'op-42', email: 'op@acme.com' });
|
|
62
|
+
Monitor.setContext('vm', { cpu: 4, ram: '8GB' });
|
|
63
|
+
Monitor.setExtra('ultimoLote', 128);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Para um escopo **isolado** (sem vazar para o global), use `withScope`:
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
Monitor.withScope((scope) => {
|
|
70
|
+
scope.setTag('lote', '128').setFingerprint(['reprocessamento']);
|
|
71
|
+
Monitor.captureMessage('Reprocessando lote 128');
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Performance: transactions e spans
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
const tx = Monitor.startTransaction('processar-lote', 'rpa.job');
|
|
79
|
+
|
|
80
|
+
const span = tx.startChild('http.client', 'GET /api/notas');
|
|
81
|
+
await api.buscarNotas();
|
|
82
|
+
span.setData('notas', 42).finish();
|
|
83
|
+
|
|
84
|
+
const filho = span.startChild('db.query', 'INSERT INTO notas'); // spans aninham
|
|
85
|
+
await gravar();
|
|
86
|
+
filho.finish();
|
|
87
|
+
|
|
88
|
+
tx.setStatus('ok'); // ou 'error'
|
|
89
|
+
tx.finish(); // só aqui a transaction é enviada (respeitando tracesSampleRate)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- `startChild(op?, description?)` existe tanto em `Transaction` quanto em `Span` (aninhamento arbitrário).
|
|
93
|
+
- Métodos encadeáveis: `setStatus`, `setData` (span), `setTag`/`setName` (transaction).
|
|
94
|
+
- `finish()` na transaction fecha spans abertos e envia o payload; nada é enviado se ela não foi amostrada.
|
|
95
|
+
|
|
96
|
+
## Crons (check-ins)
|
|
97
|
+
|
|
98
|
+
Monitora jobs agendados: um check-in `in_progress` na entrada, `ok`/`error` na saída, com duração medida. O jeito recomendado é `withMonitor`, que faz o par e captura exceções:
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
await Monitor.withMonitor(
|
|
102
|
+
'sync-notas',
|
|
103
|
+
() => executarSync(),
|
|
104
|
+
{ schedule: '0 */2 * * *', timezone: 'America/Fortaleza' }
|
|
105
|
+
);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
O `schedule` (expressão cron) faz o servidor **criar/atualizar o monitor automaticamente** (upsert) e habilita a detecção de **execução perdida**. Para controle manual, use `checkIn` diretamente (veja [Referência](./api-reference.md#checkinoptions)):
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
const id = Monitor.checkIn({ monitorSlug: 'sync-notas', status: 'in_progress' });
|
|
112
|
+
// … trabalho …
|
|
113
|
+
Monitor.checkIn({ monitorSlug: 'sync-notas', status: 'ok', checkInId: id, durationMs: 1234 });
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Heartbeat (uptime)
|
|
117
|
+
|
|
118
|
+
Bate "estou vivo" periodicamente; o servidor acusa o robô **offline** quando os batimentos param.
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
Monitor.init({
|
|
122
|
+
dsn,
|
|
123
|
+
heartbeat: { intervalMs: 30_000, name: 'Robô NFe — VM 01' },
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`heartbeat: true` usa os defaults. O `instanceId` default é o `serverName` / `HOSTNAME` / um id aleatório por processo. Campos em [HeartbeatOptions](./api-reference.md#heartbeatoptions).
|
|
128
|
+
|
|
129
|
+
## Flush e close
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
await Monitor.flush(2000); // envia o que está em buffer, mantém o SDK vivo
|
|
133
|
+
await Monitor.close(2000); // flush final + para o heartbeat + fecha o transport
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Chame `close` antes do processo sair. Os timers do Monitor são `unref`'d, então ele **não** segura o processo vivo sozinho.
|
package/docs/protocol.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Protocolo & transporte
|
|
2
|
+
|
|
3
|
+
O SDK é um **cliente HTTP puro**, sem dependências de runtime: ele só fala com o app da Hobots por HTTP. Não abre conexão com banco e **não** roda `SKIP LOCKED` — a reivindicação atômica de runs é server-side (veja o fim desta página).
|
|
4
|
+
|
|
5
|
+
Os arquivos `src/protocol.ts` e `src/manager/protocol.ts` são a **fonte de verdade do wire format**: o app valida os payloads com zod travados nesses tipos (`satisfies z.ZodType<...>`), então não podem sofrer drift.
|
|
6
|
+
|
|
7
|
+
## DSNs e endpoints derivados
|
|
8
|
+
|
|
9
|
+
| Capability | DSN | Endpoint | Autenticação |
|
|
10
|
+
| --- | --- | --- | --- |
|
|
11
|
+
| Monitor | `<proto>://<publicKey>@host/<projectId>` | `<origin>/api/ingest/<publicKey>` | public key **na URL** (sem header) |
|
|
12
|
+
| Manager | `<proto>://<managerKey>@host/<projectSlug>` | `<origin>/api/manager` | `Authorization: Bearer <managerKey>` (**nunca** na URL) |
|
|
13
|
+
|
|
14
|
+
- Monitor: `parseDsn(dsn) → { publicKey, projectId, endpoint }`.
|
|
15
|
+
- Manager: `parseManagerDsn(dsn) → { key, projectSlug, origin, baseUrl }`.
|
|
16
|
+
|
|
17
|
+
## Monitor — ingestão
|
|
18
|
+
|
|
19
|
+
Endpoint único `POST /api/ingest/<publicKey>` recebe **envelopes** JSON. O transport mantém uma fila em memória, agrupa itens em envelopes e faz POST via `fetch`.
|
|
20
|
+
|
|
21
|
+
```jsonc
|
|
22
|
+
{
|
|
23
|
+
"sdk": { "name": "hobots", "version": "1.0.0" },
|
|
24
|
+
"sent_at": "2024-01-01T00:00:00.000Z",
|
|
25
|
+
"items": [
|
|
26
|
+
{ "type": "event", "payload": { /* EventPayload */ } },
|
|
27
|
+
{ "type": "transaction", "payload": { /* TransactionPayload */ } },
|
|
28
|
+
{ "type": "check_in", "payload": { /* CheckInPayload */ } },
|
|
29
|
+
{ "type": "heartbeat", "payload": { /* HeartbeatPayload */ } }
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Tipos de item: `event`, `transaction`, `check_in`, `heartbeat` (definidos em `hobots/protocol`).
|
|
35
|
+
|
|
36
|
+
**Flush & retries:** o transport flusha por `flushIntervalMs` (default 2s) ou ao atingir `maxBatchSize` (default 10). Ladder de retry `[1s, 4s, 16s]`; respostas 4xx (exceto 429) são descartadas sem retry; a fila tem teto de 200 itens (descarta os mais antigos).
|
|
37
|
+
|
|
38
|
+
## Manager — polling e reporte
|
|
39
|
+
|
|
40
|
+
Dois endpoints sob `<origin>/api/manager`, ambos com `Authorization: Bearer <managerKey>`:
|
|
41
|
+
|
|
42
|
+
### `POST /api/manager/poll`
|
|
43
|
+
|
|
44
|
+
Corpo `ManagerPollRequest`:
|
|
45
|
+
|
|
46
|
+
```jsonc
|
|
47
|
+
{
|
|
48
|
+
"agent": { "name": "VM 01", "version": "1.0.0", "host": "vm-01", "tasks": ["processar-nfe"] },
|
|
49
|
+
"max": 1, // quantas reivindicar (1..5)
|
|
50
|
+
"tasks": ["processar-nfe"] // só runs destas tasks registradas
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Resposta `ManagerPollResponse` = `{ runs: ManagerRunDescriptor[] }`, cada `{ id, task, params, form, source, created_at }`.
|
|
55
|
+
|
|
56
|
+
**Backoff:** um request por poll; o loop decide o ritmo. HTTP 401 (key rejeitada) → retry lento a 30s com log throttled; outros erros → ladder `[1s, 4s, 16s]`.
|
|
57
|
+
|
|
58
|
+
### `PATCH /api/manager/runs/{id}`
|
|
59
|
+
|
|
60
|
+
Corpo `ManagerRunUpdateRequest`:
|
|
61
|
+
|
|
62
|
+
```jsonc
|
|
63
|
+
{
|
|
64
|
+
"status": "running", // 'running' | 'succeeded' | 'failed'
|
|
65
|
+
"logs": [{ "ts": "…", "level": "info", "message": "…" }], // delta; o servidor concatena
|
|
66
|
+
"result": { "processadas": 12 },// só com 'succeeded'
|
|
67
|
+
"error": { "type": "Error", "message": "…", "stack": "…" } // só com 'failed'
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- `status: 'running'` pode repetir para enviar **logs incrementais** (throttle por `logFlushIntervalMs`, flush ao acumular 20, chunk de 100, teto de 500 logs/run, mensagem cap 10k).
|
|
72
|
+
- Ordem garantida `running → logs → terminal` (PATCHes serializados numa cadeia de promise).
|
|
73
|
+
- Retry `[1s, 4s, 16s]`; 4xx exceto 429 é terminal (**409** = run cancelada / transição inválida → marca a run como morta e dispara `ctx.signal`).
|
|
74
|
+
- `result` é serializado com cap de ~100KB (circular/grande → descartado com marcador `__truncated`).
|
|
75
|
+
|
|
76
|
+
Ciclo de vida completo da run no servidor: `pending → claimed → running → succeeded | failed | canceled`. O agente só transiciona para `running`/`succeeded`/`failed`.
|
|
77
|
+
|
|
78
|
+
## Onde vive o claim atômico
|
|
79
|
+
|
|
80
|
+
O `POST /api/manager/poll` no app chama `claimPendingRuns()` em
|
|
81
|
+
[`apps/webapp/lib/manager/claim.ts`](../../../apps/webapp/lib/manager/claim.ts): um CTE que seleciona runs `pending` vencidas com `FOR UPDATE SKIP LOCKED` e as promove a `claimed` num único `UPDATE ... RETURNING`. Isso garante que dois agentes concorrentes nunca peguem a mesma run. **Nada disso está no SDK** — ele só faz o HTTP.
|
|
82
|
+
|
|
83
|
+
## Identidade do SDK
|
|
84
|
+
|
|
85
|
+
`SDK_NAME = 'hobots'` e `SDK_VERSION` ficam em `src/utils.ts` e vão em todo envelope (campo `sdk`) e no `agent.version` do poll.
|
|
86
|
+
|
|
87
|
+
> **Nota de manutenção:** `SDK_VERSION` é uma constante fixa e precisa ser mantida em sincronia com a `version` do `package.json` manualmente ao publicar.
|
package/package.json
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hobots",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "SDK JavaScript da Hobots — captura de erros, transactions, check-ins de cron e heartbeats (Monitor) e execução remota de tarefas sob demanda (Manager) para RPAs e integrações.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=18"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"docs"
|
|
14
|
+
],
|
|
15
|
+
"main": "./dist/index.cjs",
|
|
16
|
+
"module": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"require": "./dist/index.cjs"
|
|
23
|
+
},
|
|
24
|
+
"./protocol": {
|
|
25
|
+
"types": "./dist/protocol.d.ts",
|
|
26
|
+
"import": "./dist/protocol.js",
|
|
27
|
+
"require": "./dist/protocol.cjs"
|
|
28
|
+
},
|
|
29
|
+
"./manager": {
|
|
30
|
+
"types": "./dist/manager.d.ts",
|
|
31
|
+
"import": "./dist/manager.js",
|
|
32
|
+
"require": "./dist/manager.cjs"
|
|
33
|
+
},
|
|
34
|
+
"./manager/protocol": {
|
|
35
|
+
"types": "./dist/manager/protocol.d.ts",
|
|
36
|
+
"import": "./dist/manager/protocol.js",
|
|
37
|
+
"require": "./dist/manager/protocol.cjs"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^20",
|
|
42
|
+
"tsup": "^8.4.0",
|
|
43
|
+
"typescript": "5.7.3",
|
|
44
|
+
"@workspace/eslint-config": "0.0.0",
|
|
45
|
+
"@workspace/typescript-config": "0.0.0"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup",
|
|
49
|
+
"dev": "tsup --watch",
|
|
50
|
+
"lint": "eslint . --max-warnings 0",
|
|
51
|
+
"typecheck": "tsc --noEmit"
|
|
52
|
+
}
|
|
5
53
|
}
|
package/auth/index.d.ts
DELETED
package/csv/index.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
type FetchCSVDataOptions = {
|
|
2
|
-
tag_name: string;
|
|
3
|
-
csv_name: string;
|
|
4
|
-
csv_default_dir?: string;
|
|
5
|
-
header: string[];
|
|
6
|
-
};
|
|
7
|
-
export declare function fetchCSVData(options: FetchCSVDataOptions): Promise<any>;
|
|
8
|
-
export default fetchCSVData;
|
|
9
|
-
//# sourceMappingURL=index.d.ts.map
|
package/datasets/index.d.ts
DELETED
package/db/drizzle.config.d.ts
DELETED
package/db/index.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import * as schema from './schema';
|
|
2
|
-
import { BetterSQLite3Database as Database } from 'drizzle-orm/better-sqlite3';
|
|
3
|
-
export type { Database };
|
|
4
|
-
export * from './migrate';
|
|
5
|
-
export * from './storages';
|
|
6
|
-
export * as schema from './schema';
|
|
7
|
-
export declare function db(db_id: string): Database<typeof schema>;
|
|
8
|
-
export default db;
|
|
9
|
-
//# sourceMappingURL=index.d.ts.map
|
package/db/migrate.d.ts
DELETED
package/db/schema.d.ts
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
export declare const jobs_table: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
2
|
-
name: "jobs";
|
|
3
|
-
schema: undefined;
|
|
4
|
-
columns: {
|
|
5
|
-
id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
6
|
-
name: "id";
|
|
7
|
-
tableName: "jobs";
|
|
8
|
-
dataType: "string";
|
|
9
|
-
columnType: "SQLiteText";
|
|
10
|
-
data: string;
|
|
11
|
-
driverParam: string;
|
|
12
|
-
notNull: true;
|
|
13
|
-
hasDefault: false;
|
|
14
|
-
isPrimaryKey: true;
|
|
15
|
-
isAutoincrement: false;
|
|
16
|
-
hasRuntimeDefault: false;
|
|
17
|
-
enumValues: [string, ...string[]];
|
|
18
|
-
baseColumn: never;
|
|
19
|
-
identity: undefined;
|
|
20
|
-
generated: undefined;
|
|
21
|
-
}, {}, {
|
|
22
|
-
length: number | undefined;
|
|
23
|
-
}>;
|
|
24
|
-
job_id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
25
|
-
name: "job_id";
|
|
26
|
-
tableName: "jobs";
|
|
27
|
-
dataType: "string";
|
|
28
|
-
columnType: "SQLiteText";
|
|
29
|
-
data: string;
|
|
30
|
-
driverParam: string;
|
|
31
|
-
notNull: true;
|
|
32
|
-
hasDefault: false;
|
|
33
|
-
isPrimaryKey: false;
|
|
34
|
-
isAutoincrement: false;
|
|
35
|
-
hasRuntimeDefault: false;
|
|
36
|
-
enumValues: [string, ...string[]];
|
|
37
|
-
baseColumn: never;
|
|
38
|
-
identity: undefined;
|
|
39
|
-
generated: undefined;
|
|
40
|
-
}, {}, {
|
|
41
|
-
length: number | undefined;
|
|
42
|
-
}>;
|
|
43
|
-
schema: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
44
|
-
name: "schema";
|
|
45
|
-
tableName: "jobs";
|
|
46
|
-
dataType: "string";
|
|
47
|
-
columnType: "SQLiteText";
|
|
48
|
-
data: string;
|
|
49
|
-
driverParam: string;
|
|
50
|
-
notNull: true;
|
|
51
|
-
hasDefault: false;
|
|
52
|
-
isPrimaryKey: false;
|
|
53
|
-
isAutoincrement: false;
|
|
54
|
-
hasRuntimeDefault: false;
|
|
55
|
-
enumValues: [string, ...string[]];
|
|
56
|
-
baseColumn: never;
|
|
57
|
-
identity: undefined;
|
|
58
|
-
generated: undefined;
|
|
59
|
-
}, {}, {
|
|
60
|
-
length: number | undefined;
|
|
61
|
-
}>;
|
|
62
|
-
item_id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
63
|
-
name: "item_id";
|
|
64
|
-
tableName: "jobs";
|
|
65
|
-
dataType: "string";
|
|
66
|
-
columnType: "SQLiteText";
|
|
67
|
-
data: string;
|
|
68
|
-
driverParam: string;
|
|
69
|
-
notNull: true;
|
|
70
|
-
hasDefault: false;
|
|
71
|
-
isPrimaryKey: false;
|
|
72
|
-
isAutoincrement: false;
|
|
73
|
-
hasRuntimeDefault: false;
|
|
74
|
-
enumValues: [string, ...string[]];
|
|
75
|
-
baseColumn: never;
|
|
76
|
-
identity: undefined;
|
|
77
|
-
generated: undefined;
|
|
78
|
-
}, {}, {
|
|
79
|
-
length: number | undefined;
|
|
80
|
-
}>;
|
|
81
|
-
data: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
82
|
-
name: "data";
|
|
83
|
-
tableName: "jobs";
|
|
84
|
-
dataType: "json";
|
|
85
|
-
columnType: "SQLiteTextJson";
|
|
86
|
-
data: unknown;
|
|
87
|
-
driverParam: string;
|
|
88
|
-
notNull: true;
|
|
89
|
-
hasDefault: false;
|
|
90
|
-
isPrimaryKey: false;
|
|
91
|
-
isAutoincrement: false;
|
|
92
|
-
hasRuntimeDefault: false;
|
|
93
|
-
enumValues: undefined;
|
|
94
|
-
baseColumn: never;
|
|
95
|
-
identity: undefined;
|
|
96
|
-
generated: undefined;
|
|
97
|
-
}, {}, {}>;
|
|
98
|
-
status: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
99
|
-
name: "status";
|
|
100
|
-
tableName: "jobs";
|
|
101
|
-
dataType: "string";
|
|
102
|
-
columnType: "SQLiteText";
|
|
103
|
-
data: string;
|
|
104
|
-
driverParam: string;
|
|
105
|
-
notNull: true;
|
|
106
|
-
hasDefault: true;
|
|
107
|
-
isPrimaryKey: false;
|
|
108
|
-
isAutoincrement: false;
|
|
109
|
-
hasRuntimeDefault: false;
|
|
110
|
-
enumValues: [string, ...string[]];
|
|
111
|
-
baseColumn: never;
|
|
112
|
-
identity: undefined;
|
|
113
|
-
generated: undefined;
|
|
114
|
-
}, {}, {
|
|
115
|
-
length: number | undefined;
|
|
116
|
-
}>;
|
|
117
|
-
status_text: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
118
|
-
name: "status_text";
|
|
119
|
-
tableName: "jobs";
|
|
120
|
-
dataType: "string";
|
|
121
|
-
columnType: "SQLiteText";
|
|
122
|
-
data: string;
|
|
123
|
-
driverParam: string;
|
|
124
|
-
notNull: false;
|
|
125
|
-
hasDefault: false;
|
|
126
|
-
isPrimaryKey: false;
|
|
127
|
-
isAutoincrement: false;
|
|
128
|
-
hasRuntimeDefault: false;
|
|
129
|
-
enumValues: [string, ...string[]];
|
|
130
|
-
baseColumn: never;
|
|
131
|
-
identity: undefined;
|
|
132
|
-
generated: undefined;
|
|
133
|
-
}, {}, {
|
|
134
|
-
length: number | undefined;
|
|
135
|
-
}>;
|
|
136
|
-
started_at: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
137
|
-
name: "started_at";
|
|
138
|
-
tableName: "jobs";
|
|
139
|
-
dataType: "string";
|
|
140
|
-
columnType: "SQLiteText";
|
|
141
|
-
data: string;
|
|
142
|
-
driverParam: string;
|
|
143
|
-
notNull: false;
|
|
144
|
-
hasDefault: false;
|
|
145
|
-
isPrimaryKey: false;
|
|
146
|
-
isAutoincrement: false;
|
|
147
|
-
hasRuntimeDefault: false;
|
|
148
|
-
enumValues: [string, ...string[]];
|
|
149
|
-
baseColumn: never;
|
|
150
|
-
identity: undefined;
|
|
151
|
-
generated: undefined;
|
|
152
|
-
}, {}, {
|
|
153
|
-
length: number | undefined;
|
|
154
|
-
}>;
|
|
155
|
-
done_at: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
156
|
-
name: "done_at";
|
|
157
|
-
tableName: "jobs";
|
|
158
|
-
dataType: "string";
|
|
159
|
-
columnType: "SQLiteText";
|
|
160
|
-
data: string;
|
|
161
|
-
driverParam: string;
|
|
162
|
-
notNull: false;
|
|
163
|
-
hasDefault: false;
|
|
164
|
-
isPrimaryKey: false;
|
|
165
|
-
isAutoincrement: false;
|
|
166
|
-
hasRuntimeDefault: false;
|
|
167
|
-
enumValues: [string, ...string[]];
|
|
168
|
-
baseColumn: never;
|
|
169
|
-
identity: undefined;
|
|
170
|
-
generated: undefined;
|
|
171
|
-
}, {}, {
|
|
172
|
-
length: number | undefined;
|
|
173
|
-
}>;
|
|
174
|
-
created_at: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
175
|
-
name: "created_at";
|
|
176
|
-
tableName: "jobs";
|
|
177
|
-
dataType: "string";
|
|
178
|
-
columnType: "SQLiteText";
|
|
179
|
-
data: string;
|
|
180
|
-
driverParam: string;
|
|
181
|
-
notNull: true;
|
|
182
|
-
hasDefault: true;
|
|
183
|
-
isPrimaryKey: false;
|
|
184
|
-
isAutoincrement: false;
|
|
185
|
-
hasRuntimeDefault: false;
|
|
186
|
-
enumValues: [string, ...string[]];
|
|
187
|
-
baseColumn: never;
|
|
188
|
-
identity: undefined;
|
|
189
|
-
generated: undefined;
|
|
190
|
-
}, {}, {
|
|
191
|
-
length: number | undefined;
|
|
192
|
-
}>;
|
|
193
|
-
updated_at: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
194
|
-
name: "updated_at";
|
|
195
|
-
tableName: "jobs";
|
|
196
|
-
dataType: "string";
|
|
197
|
-
columnType: "SQLiteText";
|
|
198
|
-
data: string;
|
|
199
|
-
driverParam: string;
|
|
200
|
-
notNull: true;
|
|
201
|
-
hasDefault: true;
|
|
202
|
-
isPrimaryKey: false;
|
|
203
|
-
isAutoincrement: false;
|
|
204
|
-
hasRuntimeDefault: false;
|
|
205
|
-
enumValues: [string, ...string[]];
|
|
206
|
-
baseColumn: never;
|
|
207
|
-
identity: undefined;
|
|
208
|
-
generated: undefined;
|
|
209
|
-
}, {}, {
|
|
210
|
-
length: number | undefined;
|
|
211
|
-
}>;
|
|
212
|
-
};
|
|
213
|
-
dialect: "sqlite";
|
|
214
|
-
}>;
|
|
215
|
-
//# sourceMappingURL=schema.d.ts.map
|
package/db/storages/base.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { Database, schema } from '../index';
|
|
2
|
-
import { SQLiteTable } from 'drizzle-orm/sqlite-core';
|
|
3
|
-
export type TableModel<T extends SQLiteTable> = T;
|
|
4
|
-
export declare class Storage {
|
|
5
|
-
static db: Database<typeof schema>;
|
|
6
|
-
static model: TableModel<any>;
|
|
7
|
-
static transaction(fn: (tx: any) => Promise<any>): Promise<any>;
|
|
8
|
-
static query(tx?: Database): any;
|
|
9
|
-
static findById(id: any, options?: any, { tx }?: any): Promise<any>;
|
|
10
|
-
static findAll(where?: any, options?: any, { tx }?: any): Promise<any>;
|
|
11
|
-
static findOne(where: any, options?: any, { tx }?: any): Promise<any>;
|
|
12
|
-
static findOrCreate(where: any, data: any, opts?: any): Promise<any[]>;
|
|
13
|
-
static count(where?: any, opts?: any): Promise<number>;
|
|
14
|
-
static updateOrCreate(data: any, target?: any, opts?: any): Promise<any>;
|
|
15
|
-
static create(data: any, opts?: any): Promise<any>;
|
|
16
|
-
static bulkCreate(data: any, opts?: any): Promise<any>;
|
|
17
|
-
static update(id: any, data: any, opts?: any): Promise<any>;
|
|
18
|
-
static bulkUpdate(where: any, data: any, opts?: any): Promise<any>;
|
|
19
|
-
static destroy(where: any, opts?: any): Promise<any>;
|
|
20
|
-
static deleteById(id: any, opts?: any): Promise<any>;
|
|
21
|
-
}
|
|
22
|
-
export default Storage;
|
|
23
|
-
//# sourceMappingURL=base.d.ts.map
|
package/db/storages/index.d.ts
DELETED