@orkastery/cli 0.2.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 +87 -0
- package/adapters/README.md +22 -0
- package/adapters/claude-code/.claude-plugin/marketplace.json +15 -0
- package/adapters/claude-code/.claude-plugin/plugin.json +31 -0
- package/adapters/claude-code/README.md +102 -0
- package/adapters/claude-code/agents/ork-check.md +32 -0
- package/adapters/claude-code/agents/ork-go.md +32 -0
- package/adapters/claude-code/agents/ork-goal.md +33 -0
- package/adapters/claude-code/agents/ork-master.md +32 -0
- package/adapters/claude-code/agents/ork-plan.md +32 -0
- package/adapters/claude-code/agents/ork-ship.md +32 -0
- package/adapters/claude-code/commands/check.md +45 -0
- package/adapters/claude-code/commands/go.md +46 -0
- package/adapters/claude-code/commands/goal.md +54 -0
- package/adapters/claude-code/commands/master.md +42 -0
- package/adapters/claude-code/commands/ork.md +37 -0
- package/adapters/claude-code/commands/plan.md +47 -0
- package/adapters/claude-code/commands/ship.md +45 -0
- package/adapters/claude-code/hooks/hooks.json +17 -0
- package/adapters/claude-code/hooks/ork-guard.js +130 -0
- package/adapters/hermes/README.md +45 -0
- package/adapters/hermes/bin/ork-abrir-thread.sh +25 -0
- package/adapters/hermes/hermes.plugin.json +21 -0
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +116 -0
- package/adapters/openclaw/README.md +63 -0
- package/adapters/openclaw/bin/ork-abrir-thread.sh +24 -0
- package/adapters/openclaw/openclaw.plugin.json +138 -0
- package/dist/adapters/claude-bg.js +308 -0
- package/dist/auditoria.js +848 -0
- package/dist/auditrun.js +976 -0
- package/dist/board.js +314 -0
- package/dist/canarios.js +271 -0
- package/dist/catalogo.js +124 -0
- package/dist/ciclos.js +156 -0
- package/dist/claims.js +226 -0
- package/dist/divida.js +374 -0
- package/dist/doctor.js +246 -0
- package/dist/evalrunner.js +458 -0
- package/dist/fix.js +450 -0
- package/dist/gates.js +92 -0
- package/dist/handoff.js +521 -0
- package/dist/hosts.js +331 -0
- package/dist/index.js +1910 -0
- package/dist/init.js +231 -0
- package/dist/leases.js +508 -0
- package/dist/ledger.js +138 -0
- package/dist/manifest.js +255 -0
- package/dist/master.js +453 -0
- package/dist/memoria.js +773 -0
- package/dist/modos.js +220 -0
- package/dist/orkmind.js +487 -0
- package/dist/orquestracao.js +559 -0
- package/dist/phase.js +572 -0
- package/dist/policies.js +176 -0
- package/dist/prompts.js +406 -0
- package/dist/ratelimit.js +179 -0
- package/dist/recall.js +252 -0
- package/dist/retry.js +917 -0
- package/dist/sandbox.js +94 -0
- package/dist/sessoes.js +104 -0
- package/dist/ship.js +551 -0
- package/dist/slug.js +100 -0
- package/dist/superficie.js +1347 -0
- package/dist/thread.js +333 -0
- package/dist/tokens.js +228 -0
- package/dist/types.js +20 -0
- package/dist/util.js +156 -0
- package/dist/verify.js +262 -0
- package/dist/worktree.js +576 -0
- package/dist/yaml.js +112 -0
- package/eval/README.md +85 -0
- package/eval/casos/check-quality.json +103 -0
- package/eval/casos/code-reviewer.json +102 -0
- package/eval/casos/decision-triage.json +103 -0
- package/eval/casos/go-implementation.json +103 -0
- package/eval/casos/goal-definition.json +103 -0
- package/eval/casos/master-metrics.json +103 -0
- package/eval/casos/narrative-guardian.json +163 -0
- package/eval/casos/orkastery-bootstrap.json +102 -0
- package/eval/casos/plan-specification.json +102 -0
- package/eval/casos/roadmap-keeper.json +103 -0
- package/eval/casos/scope-check-capability-map.json +83 -0
- package/eval/casos/security-auditor.json +102 -0
- package/eval/casos/ship-release.json +122 -0
- package/eval/casos/test-engineer.json +122 -0
- package/eval/casos/thread-state.json +82 -0
- package/eval/casos/thread-tracing.json +83 -0
- package/eval/casos/web-performance-auditor.json +102 -0
- package/eval/fixtures/b0-slug-e-modos/caso.json +56 -0
- package/eval/fixtures/b2-master-log/caso.json +91 -0
- package/eval/fixtures/fx-concurrency/caso.json +15 -0
- package/eval/fixtures/fx-hallucination/caso.json +12 -0
- package/eval/fixtures/fx-happy/caso.json +16 -0
- package/eval/fixtures/fx-schema-drift/caso.json +15 -0
- package/eval/fixtures/fx-stale-base/caso.json +12 -0
- package/eval/fixtures/fx-wiki-destroy/caso.json +16 -0
- package/eval/fixtures/superficie-de-rede/README.md +22 -0
- package/eval/fixtures/superficie-de-rede/api-express.js +30 -0
- package/eval/fixtures/superficie-de-rede/api_fastapi.py +17 -0
- package/eval/fixtures/superficie-de-rede/api_gin.go +18 -0
- package/package.json +56 -0
- package/references/README.md +25 -0
- package/references/code-review-axes.md +82 -0
- package/references/definition-of-done.md +74 -0
- package/references/performance-checklist.md +53 -0
- package/references/security-checklist.md +101 -0
- package/references/testing-patterns.md +51 -0
- package/skills/README.md +43 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +89 -0
- package/skills/core/thread-state/SKILL.md +86 -0
- package/skills/governance/decision-triage/SKILL.md +65 -0
- package/skills/governance/narrative-guardian/SKILL.md +66 -0
- package/skills/governance/roadmap-keeper/SKILL.md +60 -0
- package/skills/governance/scope-check-capability-map/SKILL.md +61 -0
- package/skills/observability/thread-tracing/SKILL.md +63 -0
- package/skills/phases/check-quality/SKILL.md +87 -0
- package/skills/phases/go-implementation/SKILL.md +70 -0
- package/skills/phases/goal-definition/SKILL.md +69 -0
- package/skills/phases/master-metrics/SKILL.md +65 -0
- package/skills/phases/plan-specification/SKILL.md +67 -0
- package/skills/phases/ship-release/SKILL.md +68 -0
- package/skills/reviewers/code-reviewer/SKILL.md +61 -0
- package/skills/reviewers/security-auditor/SKILL.md +68 -0
- package/skills/reviewers/test-engineer/SKILL.md +66 -0
- package/skills/reviewers/web-performance-auditor/SKILL.md +63 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "orkastery",
|
|
3
|
+
"displayName": "Orkastery",
|
|
4
|
+
"version": "{{versao}}",
|
|
5
|
+
"description": "Conducao de looping threads em 6 fases pelo nucleo `ork`, exposta ao OpenClaw como tools `ork_*`. Zero regra de negocio no host: cada tool e uma chamada de CLI.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"runtime": { "type": "cli", "command": "{{ork_bin}}" },
|
|
8
|
+
"conduction": {
|
|
9
|
+
"tags": ["#Look", "#Ork", "#Classic", "#Maestro", "#Auto"],
|
|
10
|
+
"parse": "ork_modo_do_pedido",
|
|
11
|
+
"nota": "O host transporta a tag. A validacao contra conduction.allowed_modes e do nucleo, no ork thread new."
|
|
12
|
+
},
|
|
13
|
+
"tools": [
|
|
14
|
+
{
|
|
15
|
+
"name": "ork_doctor",
|
|
16
|
+
"description": "O que vale nesta maquina agora: runtime, assinatura, provider de custo. Sai diferente de zero quando o despacho nao vale.",
|
|
17
|
+
"command": ["{{ork_bin}}", "doctor"],
|
|
18
|
+
"parameters": {}
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "ork_modo_do_pedido",
|
|
22
|
+
"description": "Le a #TAG de conducao do pedido do builder (#Look, #Ork, #Classic, #Maestro, #Auto) e devolve o modo. Sem tag, devolve o conduction.default_mode do manifesto. Use SEMPRE este tool em vez de reconhecer a tag no host.",
|
|
23
|
+
"command": ["{{ork_bin}}", "modos", "--do-pedido", "{pedido}", "--json"],
|
|
24
|
+
"parameters": {
|
|
25
|
+
"pedido": { "type": "string", "required": true, "description": "O texto inteiro do pedido do builder" }
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "ork_thread_new",
|
|
30
|
+
"description": "Cria a thread, o slug de 3 partes e o ledger. O modo vem de ork_modo_do_pedido; o nucleo valida contra conduction.allowed_modes e recusa o que o projeto nao permite.",
|
|
31
|
+
"command": ["{{ork_bin}}", "thread", "new", "{nome}", "--mode", "{modo}", "--worktree", "auto"],
|
|
32
|
+
"parameters": {
|
|
33
|
+
"nome": { "type": "string", "required": true, "description": "Nome curto da demanda" },
|
|
34
|
+
"modo": { "type": "string", "required": true, "description": "look|default|ork|maestro|auto, vindo de ork_modo_do_pedido" }
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "ork_thread_status",
|
|
39
|
+
"description": "Estado da thread lido do disco e cruzado com o runtime real.",
|
|
40
|
+
"command": ["{{ork_bin}}", "thread", "status", "{thread}"],
|
|
41
|
+
"parameters": { "thread": { "type": "string", "required": true } }
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "ork_phase_run",
|
|
45
|
+
"description": "Despacha uma fase (GOAL PLAN GO CHECK SHIP MASTER) pelo runtime adapter, gravando o prompt exato com sha256 no ledger.",
|
|
46
|
+
"command": ["{{ork_bin}}", "phase", "run", "{thread}", "{fase}", "--prompt", "{prompt}"],
|
|
47
|
+
"parameters": {
|
|
48
|
+
"thread": { "type": "string", "required": true },
|
|
49
|
+
"fase": { "type": "string", "required": true, "enum": ["GOAL", "PLAN", "GO", "CHECK", "SHIP", "MASTER"] },
|
|
50
|
+
"prompt": { "type": "string", "required": true, "description": "O pedido do builder para esta fase" }
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "ork_phase_list",
|
|
55
|
+
"description": "O ledger da thread, evento a evento, com quem decidiu e a evidencia.",
|
|
56
|
+
"command": ["{{ork_bin}}", "phase", "list", "{thread}"],
|
|
57
|
+
"parameters": { "thread": { "type": "string", "required": true } }
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "ork_claims_add",
|
|
61
|
+
"description": "Registra uma alegacao verificavel com o comando que a comprova. Toda citacao de arquivo ou teste vira claim.",
|
|
62
|
+
"command": ["{{ork_bin}}", "claims", "add", "{thread}", "{arquivo}", "--claim", "{alegacao}", "--verificar", "{comando}"],
|
|
63
|
+
"parameters": {
|
|
64
|
+
"thread": { "type": "string", "required": true },
|
|
65
|
+
"arquivo": { "type": "string", "required": true },
|
|
66
|
+
"alegacao": { "type": "string", "required": true },
|
|
67
|
+
"comando": { "type": "string", "required": true }
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "ork_verify",
|
|
72
|
+
"description": "Reexecuta claims e comandos do manifesto no HEAD real e compara com a baseline. Passagem relatada nao e passagem.",
|
|
73
|
+
"command": ["{{ork_bin}}", "verify", "{thread}"],
|
|
74
|
+
"parameters": { "thread": { "type": "string", "required": true } }
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "ork_verify_baseline",
|
|
78
|
+
"description": "Grava a baseline: o estado do mundo ANTES do GO, que separa regressao de divida pre-existente.",
|
|
79
|
+
"command": ["{{ork_bin}}", "verify", "{thread}", "--baseline"],
|
|
80
|
+
"parameters": { "thread": { "type": "string", "required": true } }
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "ork_worktree_ensure",
|
|
84
|
+
"description": "Garante a worktree isolada da thread, com a base resolvida pelo nucleo.",
|
|
85
|
+
"command": ["{{ork_bin}}", "worktree", "ensure", "{thread}"],
|
|
86
|
+
"parameters": { "thread": { "type": "string", "required": true } }
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "ork_worktree_audit",
|
|
90
|
+
"description": "Confere a worktree no proprio git. Sai diferente de zero quando ela divergiu ou a base avancou.",
|
|
91
|
+
"command": ["{{ork_bin}}", "worktree", "audit", "{thread}"],
|
|
92
|
+
"parameters": { "thread": { "type": "string", "required": true } }
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"name": "ork_gate_approve",
|
|
96
|
+
"description": "Registra no ledger a autorizacao humana de um gate (objetivo, premissas, evidencias, push, score).",
|
|
97
|
+
"command": ["{{ork_bin}}", "gate", "approve", "{thread}", "{sobre}", "--por", "{quem}"],
|
|
98
|
+
"parameters": {
|
|
99
|
+
"thread": { "type": "string", "required": true },
|
|
100
|
+
"sobre": { "type": "string", "required": true },
|
|
101
|
+
"quem": { "type": "string", "required": true }
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"name": "ork_ship",
|
|
106
|
+
"description": "Merge serializado por lease e push PROVADO comparando o sha local com o que o remoto reporta.",
|
|
107
|
+
"command": ["{{ork_bin}}", "ship", "{thread}", "--para", "{base}", "--autorizar-push", "{quem}"],
|
|
108
|
+
"parameters": {
|
|
109
|
+
"thread": { "type": "string", "required": true },
|
|
110
|
+
"base": { "type": "string", "required": true },
|
|
111
|
+
"quem": { "type": "string", "required": true }
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "ork_master",
|
|
116
|
+
"description": "Fecha a thread: POSTMORTEM tipado, MASTER log no contrato congelado e o score HUMANO de 0 a 5 com justificativa. Nunca invente a nota.",
|
|
117
|
+
"command": ["{{ork_bin}}", "master", "{thread}", "--score", "{score}", "--justificativa", "{justificativa}", "--por", "{quem}"],
|
|
118
|
+
"parameters": {
|
|
119
|
+
"thread": { "type": "string", "required": true },
|
|
120
|
+
"score": { "type": "string", "required": true, "description": "inteiro de 0 a 5, dado pelo humano" },
|
|
121
|
+
"justificativa": { "type": "string", "required": true },
|
|
122
|
+
"quem": { "type": "string", "required": true }
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"name": "ork_board",
|
|
127
|
+
"description": "Todas as threads em uma visao, e o escalonador dizendo quem avanca agora e quem espera.",
|
|
128
|
+
"command": ["{{ork_bin}}", "board", "plan"],
|
|
129
|
+
"parameters": {}
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"name": "ork_master_batch",
|
|
133
|
+
"description": "A fila de score dos modos sem pausa de MASTER. Fila que ninguem passa e score que nao existe.",
|
|
134
|
+
"command": ["{{ork_bin}}", "master", "--batch"],
|
|
135
|
+
"parameters": {}
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Adapter de runtime `claude-bg`: despacha fases como BACKGROUND AGENT do Claude Code.
|
|
4
|
+
*
|
|
5
|
+
* Mecanismo validado em producao (02/09) e os dois pitfalls que ele evita:
|
|
6
|
+
* - `claude -p` e efemero e invisivel no app e no `claude agents`. NUNCA usar.
|
|
7
|
+
* - `claude remote-control --spawn=session` trava sem registrar a sessao. NUNCA usar.
|
|
8
|
+
* O que vale: `claude --bg "<prompt>" --name <slug>` rodando com cwd no diretorio do projeto,
|
|
9
|
+
* o que deixa a sessao visivel no app Claude e no `claude agents --json`.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.NOME_ADAPTER = void 0;
|
|
13
|
+
exports.disponivel = disponivel;
|
|
14
|
+
exports.versao = versao;
|
|
15
|
+
exports.montarComando = montarComando;
|
|
16
|
+
exports.extrairSessionId = extrairSessionId;
|
|
17
|
+
exports.listarSessoes = listarSessoes;
|
|
18
|
+
exports.acharSessao = acharSessao;
|
|
19
|
+
exports.despachar = despachar;
|
|
20
|
+
exports.parseRateLimit = parseRateLimit;
|
|
21
|
+
exports.comandoAttach = comandoAttach;
|
|
22
|
+
exports.limparAnsi = limparAnsi;
|
|
23
|
+
exports.logs = logs;
|
|
24
|
+
exports.parar = parar;
|
|
25
|
+
exports.ocupacaoDeContexto = ocupacaoDeContexto;
|
|
26
|
+
const util_1 = require("../util");
|
|
27
|
+
exports.NOME_ADAPTER = 'claude-bg';
|
|
28
|
+
/** O runtime esta disponivel nesta maquina? */
|
|
29
|
+
function disponivel() {
|
|
30
|
+
return (0, util_1.noPath)('claude');
|
|
31
|
+
}
|
|
32
|
+
/** Versao do runtime, para o `ork doctor`. */
|
|
33
|
+
function versao() {
|
|
34
|
+
const r = (0, util_1.exec)('claude', ['--version'], undefined, 30000);
|
|
35
|
+
return r.ok ? r.stdout.trim() : null;
|
|
36
|
+
}
|
|
37
|
+
/** Monta a linha de comando exata do despacho (usada tambem pelo `--dry-run`). */
|
|
38
|
+
function montarComando(pedido) {
|
|
39
|
+
const args = ['--bg', pedido.prompt, '--name', pedido.nome];
|
|
40
|
+
if (pedido.model)
|
|
41
|
+
args.push('--model', pedido.model);
|
|
42
|
+
if (pedido.effort)
|
|
43
|
+
args.push('--effort', pedido.effort);
|
|
44
|
+
return ['claude', ...args];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Extrai o identificador da sessao da saida do `claude --bg`.
|
|
48
|
+
* Aceita UUID completo ou o id curto que o CLI imprime.
|
|
49
|
+
*/
|
|
50
|
+
function extrairSessionId(saida) {
|
|
51
|
+
const uuid = saida.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i);
|
|
52
|
+
if (uuid)
|
|
53
|
+
return uuid[0];
|
|
54
|
+
const curto = saida.match(/\b[0-9a-f]{8}\b/i);
|
|
55
|
+
return curto ? curto[0] : null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Lista as sessoes vivas do runtime. Fonte da verdade externa, nunca self-report.
|
|
59
|
+
*
|
|
60
|
+
* O filtro por diretorio e feito aqui, e nao pelo `--cwd` do `claude agents`: medido
|
|
61
|
+
* em 02/09/2026 na versao 2.1.259, `claude agents --json --cwd <raiz>` devolve lista
|
|
62
|
+
* vazia mesmo com sessoes vivas naquele diretorio.
|
|
63
|
+
*/
|
|
64
|
+
function listarSessoes(cwd, todas = false) {
|
|
65
|
+
const args = ['agents', '--json'];
|
|
66
|
+
if (todas)
|
|
67
|
+
args.push('--all');
|
|
68
|
+
const r = (0, util_1.exec)('claude', args, undefined, 60000);
|
|
69
|
+
if (!r.ok)
|
|
70
|
+
return [];
|
|
71
|
+
let dados;
|
|
72
|
+
try {
|
|
73
|
+
const bruto = JSON.parse(r.stdout);
|
|
74
|
+
dados = Array.isArray(bruto) ? bruto : [];
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
if (!cwd)
|
|
80
|
+
return dados;
|
|
81
|
+
const raiz = cwd.replace(/\/$/, '');
|
|
82
|
+
return dados.filter((s) => s.cwd === raiz || (s.cwd ?? '').startsWith(raiz + '/'));
|
|
83
|
+
}
|
|
84
|
+
/** Procura uma sessao pelo id curto, id completo ou nome. */
|
|
85
|
+
function acharSessao(chave, sessoes) {
|
|
86
|
+
const lista = sessoes ?? listarSessoes(undefined, true);
|
|
87
|
+
return (lista.find((s) => s.sessionId === chave || s.id === chave || s.name === chave) ??
|
|
88
|
+
lista.find((s) => s.sessionId?.startsWith(chave)) ??
|
|
89
|
+
null);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Despacha a fase e RE-VERIFICA no runtime que a sessao existe de fato.
|
|
93
|
+
* Nao confiar no self-report do despacho e a regra 4 da paridade.
|
|
94
|
+
*/
|
|
95
|
+
function despachar(pedido) {
|
|
96
|
+
const comando = montarComando(pedido);
|
|
97
|
+
if (pedido.dryRun) {
|
|
98
|
+
return { ok: true, comando, sessionId: null, verificada: false, stdout: '', stderr: '' };
|
|
99
|
+
}
|
|
100
|
+
if (!disponivel()) {
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
comando,
|
|
104
|
+
sessionId: null,
|
|
105
|
+
verificada: false,
|
|
106
|
+
stdout: '',
|
|
107
|
+
stderr: '',
|
|
108
|
+
erro: 'binario `claude` nao encontrado no PATH',
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const r = (0, util_1.exec)(comando[0], comando.slice(1), pedido.cwd, 180000);
|
|
112
|
+
const saida = r.stdout + '\n' + r.stderr;
|
|
113
|
+
const sessionId = extrairSessionId(saida);
|
|
114
|
+
if (!r.ok || !sessionId) {
|
|
115
|
+
// Bloco B3: rate limit nao e "runtime indisponivel". Ele tem horario de reset, e por
|
|
116
|
+
// isso volta tipado daqui para virar pedido na fila duravel em vez de falha seca.
|
|
117
|
+
const rateLimit = parseRateLimit(saida);
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
comando,
|
|
121
|
+
sessionId,
|
|
122
|
+
verificada: false,
|
|
123
|
+
stdout: r.stdout,
|
|
124
|
+
stderr: r.stderr,
|
|
125
|
+
rateLimit,
|
|
126
|
+
erro: rateLimit
|
|
127
|
+
? `limite de uso do runtime atingido: ${rateLimit.trecho}`
|
|
128
|
+
: `despacho falhou (code ${r.code})`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const achada = acharSessao(sessionId);
|
|
132
|
+
const verificada = achada !== null || acharSessao(pedido.nome) !== null;
|
|
133
|
+
return {
|
|
134
|
+
ok: true,
|
|
135
|
+
comando,
|
|
136
|
+
sessionId: achada?.sessionId ?? sessionId,
|
|
137
|
+
verificada,
|
|
138
|
+
stdout: r.stdout,
|
|
139
|
+
stderr: r.stderr,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* O texto diz que o limite de uso da assinatura foi atingido?
|
|
144
|
+
*
|
|
145
|
+
* Reconhecimento por FRASE do runtime, nao por codigo de saida: o `claude --bg` sai com
|
|
146
|
+
* codigo generico e a unica evidencia do rate limit e o texto no stderr.
|
|
147
|
+
*/
|
|
148
|
+
const FRASES_DE_RATE_LIMIT = [
|
|
149
|
+
/usage limit reached/i,
|
|
150
|
+
/\brate[ _-]?limit(?:ed|s)?\b/i,
|
|
151
|
+
/too many requests/i,
|
|
152
|
+
/\b429\b/,
|
|
153
|
+
/quota exceeded/i,
|
|
154
|
+
/out of (?:usage|weekly|session) limit/i,
|
|
155
|
+
];
|
|
156
|
+
/**
|
|
157
|
+
* Padroes de horario de reset, do mais preciso para o menos.
|
|
158
|
+
*
|
|
159
|
+
* A ordem importa: epoch e ISO sao horario ABSOLUTO dito pelo runtime; relogio e
|
|
160
|
+
* duracao precisam da hora de agora para virar carimbo. Nenhum deles inventa nada.
|
|
161
|
+
*/
|
|
162
|
+
function casarEpoch(texto) {
|
|
163
|
+
// Formato real do CLI: `Claude AI usage limit reached|1757012400`.
|
|
164
|
+
const m = texto.match(/(?:limit reached|reset[^\n|]*)\|\s*(\d{10,13})\b/i);
|
|
165
|
+
if (!m)
|
|
166
|
+
return null;
|
|
167
|
+
const bruto = Number(m[1]);
|
|
168
|
+
const ms = m[1].length >= 13 ? bruto : bruto * 1000;
|
|
169
|
+
return { resetEm: new Date(ms).toISOString(), trecho: m[0].trim() };
|
|
170
|
+
}
|
|
171
|
+
function casarIso(texto) {
|
|
172
|
+
const m = texto.match(/(?:reset|available|try again|retry)[^\n]{0,40}?(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2})?(?:Z|[+-]\d{2}:?\d{2})?)/i);
|
|
173
|
+
if (!m)
|
|
174
|
+
return null;
|
|
175
|
+
const data = new Date(m[1].replace(' ', 'T'));
|
|
176
|
+
if (isNaN(data.getTime()))
|
|
177
|
+
return null;
|
|
178
|
+
return { resetEm: data.toISOString(), trecho: m[0].trim() };
|
|
179
|
+
}
|
|
180
|
+
function casarDuracao(texto, agoraMs) {
|
|
181
|
+
const m = texto.match(
|
|
182
|
+
// A unidade aceita plural (`25 minutes`): fechar em `\b` sem o `s?` fazia o padrao
|
|
183
|
+
// inteiro falhar justamente na forma que o runtime mais usa.
|
|
184
|
+
/(?:try again|retry|reset[a-z]*|available|wait)[^\n]{0,20}?\bin\b\s*(\d+)\s*(seconds?|secs?|minutes?|mins?|hours?|hrs?|s|m|h)\b|retry[- ]after[:\s]+(\d+)/i);
|
|
185
|
+
if (!m)
|
|
186
|
+
return null;
|
|
187
|
+
if (m[3] !== undefined) {
|
|
188
|
+
// `Retry-After: 3600` e sempre em segundos.
|
|
189
|
+
return {
|
|
190
|
+
resetEm: new Date(agoraMs + Number(m[3]) * 1000).toISOString(),
|
|
191
|
+
trecho: m[0].trim(),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const valor = Number(m[1]);
|
|
195
|
+
const unidade = (m[2] ?? '').toLowerCase();
|
|
196
|
+
const segundos = unidade.startsWith('h')
|
|
197
|
+
? valor * 3600
|
|
198
|
+
: unidade.startsWith('m')
|
|
199
|
+
? valor * 60
|
|
200
|
+
: valor;
|
|
201
|
+
return { resetEm: new Date(agoraMs + segundos * 1000).toISOString(), trecho: m[0].trim() };
|
|
202
|
+
}
|
|
203
|
+
function casarRelogio(texto, agoraMs) {
|
|
204
|
+
const m = texto.match(/reset[a-z]*\s*(?:at|em|as)?\s*(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\b/i);
|
|
205
|
+
if (!m)
|
|
206
|
+
return null;
|
|
207
|
+
let hora = Number(m[1]);
|
|
208
|
+
const minuto = Number(m[2] ?? '0');
|
|
209
|
+
const sufixo = (m[3] ?? '').toLowerCase();
|
|
210
|
+
if (sufixo === 'pm' && hora < 12)
|
|
211
|
+
hora += 12;
|
|
212
|
+
if (sufixo === 'am' && hora === 12)
|
|
213
|
+
hora = 0;
|
|
214
|
+
if (hora > 23 || minuto > 59)
|
|
215
|
+
return null;
|
|
216
|
+
// Proxima ocorrencia daquela hora de relogio a partir de agora, no fuso da maquina.
|
|
217
|
+
const base = new Date(agoraMs);
|
|
218
|
+
const alvo = new Date(base.getFullYear(), base.getMonth(), base.getDate(), hora, minuto, 0, 0);
|
|
219
|
+
if (alvo.getTime() <= agoraMs)
|
|
220
|
+
alvo.setDate(alvo.getDate() + 1);
|
|
221
|
+
return { resetEm: alvo.toISOString(), trecho: m[0].trim() };
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Extrai o sinal de rate limit da saida real do runtime (stdout + stderr).
|
|
225
|
+
*
|
|
226
|
+
* Devolve `null` quando o texto nao fala de limite de uso. Quando fala mas NAO diz a
|
|
227
|
+
* hora, devolve `resetEm: null` com fonte `sem-horario`: quem decide a janela padrao e
|
|
228
|
+
* a fila do B3, com a estimativa declarada. Chutar um horario aqui seria o mesmo defeito
|
|
229
|
+
* de inventar ocupacao de janela no gate de tokens.
|
|
230
|
+
*/
|
|
231
|
+
function parseRateLimit(saida, agoraMs = Date.now()) {
|
|
232
|
+
const texto = saida ?? '';
|
|
233
|
+
const frase = FRASES_DE_RATE_LIMIT.find((r) => r.test(texto));
|
|
234
|
+
if (!frase)
|
|
235
|
+
return null;
|
|
236
|
+
const epoch = casarEpoch(texto);
|
|
237
|
+
if (epoch)
|
|
238
|
+
return { resetEm: epoch.resetEm, fonte: 'epoch', trecho: epoch.trecho };
|
|
239
|
+
const iso = casarIso(texto);
|
|
240
|
+
if (iso)
|
|
241
|
+
return { resetEm: iso.resetEm, fonte: 'iso', trecho: iso.trecho };
|
|
242
|
+
const duracao = casarDuracao(texto, agoraMs);
|
|
243
|
+
if (duracao)
|
|
244
|
+
return { resetEm: duracao.resetEm, fonte: 'duracao', trecho: duracao.trecho };
|
|
245
|
+
const relogio = casarRelogio(texto, agoraMs);
|
|
246
|
+
if (relogio)
|
|
247
|
+
return { resetEm: relogio.resetEm, fonte: 'relogio', trecho: relogio.trecho };
|
|
248
|
+
const linha = texto.split('\n').find((l) => frase.test(l)) ?? texto;
|
|
249
|
+
return { resetEm: null, fonte: 'sem-horario', trecho: linha.trim().slice(0, 200) };
|
|
250
|
+
}
|
|
251
|
+
/** Comando de anexar a sessao (precisa de TTY, entao o CLI imprime em vez de executar). */
|
|
252
|
+
function comandoAttach(sessionId) {
|
|
253
|
+
return `claude attach ${sessionId}`;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Transforma o dump de tela que o `claude logs` devolve em texto legivel.
|
|
257
|
+
*
|
|
258
|
+
* O dump e uma gravacao de terminal: as linhas sao separadas por movimento de cursor,
|
|
259
|
+
* nao por quebra de linha. Por isso o movimento vira quebra ANTES de as demais
|
|
260
|
+
* sequencias ANSI serem removidas, senao a saida inteira colapsa numa linha so.
|
|
261
|
+
*/
|
|
262
|
+
function limparAnsi(bruto) {
|
|
263
|
+
return bruto
|
|
264
|
+
.replace(/\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)/g, '')
|
|
265
|
+
.replace(/\u001b\[[0-9;]*[HfABEd]/g, '\n')
|
|
266
|
+
.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g, '')
|
|
267
|
+
.replace(/\u001b[()][A-Za-z0-9]/g, '')
|
|
268
|
+
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, '')
|
|
269
|
+
.replace(/[ \t]+$/gm, '')
|
|
270
|
+
.replace(/\n{3,}/g, '\n\n');
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Le os logs de uma sessao.
|
|
274
|
+
*
|
|
275
|
+
* O `claude logs` aceita o id CURTO da sessao (o campo `id` de `claude agents --json`),
|
|
276
|
+
* nao o UUID completo: passar o UUID responde "No job matching". Por isso a chave e
|
|
277
|
+
* resolvida antes, e a saida (dump de tela com ANSI) e limpa aqui.
|
|
278
|
+
*/
|
|
279
|
+
function logs(chave, linhas = 60) {
|
|
280
|
+
const sessao = acharSessao(chave);
|
|
281
|
+
const id = sessao?.id ?? (sessao?.sessionId ?? chave).slice(0, 8);
|
|
282
|
+
const r = (0, util_1.exec)('claude', ['logs', id], undefined, 60000);
|
|
283
|
+
const bruto = limparAnsi(r.ok ? r.stdout : r.stdout + r.stderr);
|
|
284
|
+
const uteis = bruto.split('\n').filter((l) => l.trim() !== '');
|
|
285
|
+
return { ok: r.ok, texto: uteis.slice(-linhas).join('\n') };
|
|
286
|
+
}
|
|
287
|
+
/** Para uma sessao em background (o `claude stop` tambem trabalha com o id curto). */
|
|
288
|
+
function parar(chave) {
|
|
289
|
+
const sessao = acharSessao(chave);
|
|
290
|
+
const id = sessao?.id ?? (sessao?.sessionId ?? chave).slice(0, 8);
|
|
291
|
+
const r = (0, util_1.exec)('claude', ['stop', id], undefined, 60000);
|
|
292
|
+
return { ok: r.ok, texto: limparAnsi(r.stdout + r.stderr).trim() };
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Ocupacao da janela de contexto de uma sessao, entre 0 e 1.
|
|
296
|
+
*
|
|
297
|
+
* MEDIDO em 02/09/2026 na versao 2.1.259 do Claude Code: nem `claude agents --json` nem
|
|
298
|
+
* `claude logs` expoem uso de contexto da sessao. Entao este adapter responde `null`, e o
|
|
299
|
+
* gate de tokens declara a fonte como `unavailable`.
|
|
300
|
+
*
|
|
301
|
+
* Retornar um numero estimado aqui seria inventar dado e contaminar uma decisao real de
|
|
302
|
+
* rotacao de sessao. Quando o runtime passar a expor a metrica, e SO aqui que muda: o
|
|
303
|
+
* gate ja sabe reportar `runtime_reported` no dia em que esta funcao devolver um numero.
|
|
304
|
+
*/
|
|
305
|
+
function ocupacaoDeContexto(chave) {
|
|
306
|
+
void chave;
|
|
307
|
+
return null;
|
|
308
|
+
}
|