@orkastery/cli 0.2.0 → 0.3.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 +1 -1
- package/README.md +86 -65
- package/adapters/README.md +30 -15
- package/adapters/claude-code/.claude-plugin/plugin.json +11 -5
- package/adapters/claude-code/README.md +110 -8
- package/adapters/claude-code/commands/onboarding.md +21 -0
- package/adapters/claude-code/commands/ork.md +99 -27
- package/adapters/claude-code/hooks/hooks.json +6 -1
- package/adapters/claude-code/hooks/ork-sensor.js +66 -0
- package/adapters/codex/skills/ork/SKILL.md +57 -0
- package/adapters/hermes/README.md +163 -0
- package/adapters/hermes/bin/ork-abrir-thread.sh +2 -0
- package/adapters/hermes/bin/ork-brain.sh +4 -0
- package/adapters/hermes/bin/ork-hitl-answer.py +82 -0
- package/adapters/hermes/bin/ork-maestro.sh +6 -0
- package/adapters/hermes/bin/ork-master-enviar.py +84 -0
- package/adapters/hermes/bin/ork-objective-message.sh +17 -0
- package/adapters/hermes/bin/ork-objective-status.sh +12 -0
- package/adapters/hermes/bin/ork-pulse-enviar.py +47 -0
- package/adapters/hermes/hermes.plugin.json +26 -4
- package/adapters/hermes/hitl-ingress/__init__.py +356 -0
- package/adapters/hermes/hitl-ingress/plugin.yaml +4 -0
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +75 -22
- package/adapters/openclaw/README.md +135 -26
- package/adapters/openclaw/bin/ork-brain.sh +4 -0
- package/adapters/openclaw/construir.sh +19 -0
- package/adapters/openclaw/dist/hitl-ingress.js +199 -0
- package/adapters/openclaw/dist/index.js +437 -0
- package/adapters/openclaw/openclaw.plugin.json +39 -134
- package/adapters/openclaw/package.json +26 -0
- package/adapters/openclaw/src/hitl-ingress.ts +180 -0
- package/adapters/openclaw/src/index.ts +458 -0
- package/adapters/openclaw/src/tipos-openclaw.d.ts +58 -0
- package/adapters/openclaw/tsconfig.json +15 -0
- package/assets/docs/markdownlint-cli2.jsonc +22 -0
- package/assets/docs/padroes/documentacao-de-produto.md +133 -0
- package/assets/docs/padroes/roadmap-de-produto.md +98 -0
- package/assets/docs/produto/README.md +14 -0
- package/assets/docs/produto/_modelo-feature.md +59 -0
- package/assets/docs/roadmap/README.md +14 -0
- package/assets/docs/roadmap/_modelo-item.md +73 -0
- package/assets/orkmind-native-schema.json +32 -0
- package/assets/orkmind_bridge.py +357 -0
- package/assets/orkmind_fixture.py +67 -0
- package/assets/orkmind_prospective.py +138 -0
- package/assets/reference-tariffs-i07.json +23 -0
- package/dist/adapters/claude-bg.js +488 -39
- package/dist/adapters/codex-controller-sensor.js +449 -0
- package/dist/adapters/codex-controller-worker.js +361 -0
- package/dist/adapters/codex-controller.js +252 -0
- package/dist/adapters/codex-events.js +205 -0
- package/dist/adapters/codex-question.js +26 -0
- package/dist/adapters/codex-runner.js +133 -0
- package/dist/adapters/codex.js +394 -0
- package/dist/agents-md.js +84 -0
- package/dist/auditoria.js +11 -8
- package/dist/auditrun.js +5 -4
- package/dist/board.js +173 -22
- package/dist/branch-de-estado.js +136 -0
- package/dist/canarios-hitl.js +177 -0
- package/dist/canarios-i43.js +543 -0
- package/dist/canarios-pulse.js +147 -0
- package/dist/canarios-sensores.js +129 -0
- package/dist/canarios.js +111 -2
- package/dist/catalogo.js +9 -0
- package/dist/ci.js +216 -0
- package/dist/ciclos.js +2 -1
- package/dist/claim-lint.js +64 -0
- package/dist/claims.js +60 -0
- package/dist/company-brain-capture.js +195 -0
- package/dist/company-brain-cli.js +123 -0
- package/dist/company-brain-client.js +61 -0
- package/dist/company-brain-contract.js +166 -0
- package/dist/company-brain-journal.js +192 -0
- package/dist/company-brain-mcp.js +33 -0
- package/dist/company-brain-migration.js +60 -0
- package/dist/company-brain-source.js +177 -0
- package/dist/company-brain-worker.js +18 -0
- package/dist/conducao-texto.js +61 -0
- package/dist/conducao.js +876 -0
- package/dist/contrato-publico.js +39 -0
- package/dist/creation-operation-store.js +251 -0
- package/dist/creation-operation.js +148 -0
- package/dist/decisao-autonoma.js +183 -0
- package/dist/delegation.js +79 -0
- package/dist/demo.js +120 -0
- package/dist/docs.js +828 -0
- package/dist/doctor.js +183 -24
- package/dist/entrega-pr.js +117 -0
- package/dist/escopo-escrita.js +71 -0
- package/dist/estado-thread.js +222 -0
- package/dist/evalrunner.js +12 -0
- package/dist/fabrica-estado.js +329 -0
- package/dist/fabrica-publicar.js +76 -0
- package/dist/fix.js +21 -0
- package/dist/gates.js +55 -9
- package/dist/handoff.js +39 -23
- package/dist/hitl-canais.js +333 -0
- package/dist/hitl-classificacao.js +131 -0
- package/dist/hitl-contract.js +465 -0
- package/dist/hitl-estado.js +136 -0
- package/dist/hitl-gates.js +554 -0
- package/dist/hitl-ingress-receipt.js +382 -0
- package/dist/hitl-local-atestado.js +57 -0
- package/dist/hitl-local-receipt.js +328 -0
- package/dist/hitl-local.js +143 -0
- package/dist/hitl-lock.js +139 -0
- package/dist/hitl-lote.js +223 -0
- package/dist/hitl-native-offer.js +97 -0
- package/dist/hitl-native.js +65 -0
- package/dist/hitl-presentation.js +209 -0
- package/dist/hitl-public-receipt.js +176 -0
- package/dist/hitl-resumo.js +209 -0
- package/dist/hitl-sessions.js +475 -0
- package/dist/hitl.js +711 -0
- package/dist/horario.js +269 -0
- package/dist/hosts.js +198 -22
- package/dist/index.js +1736 -93
- package/dist/indice.js +99 -0
- package/dist/init.js +28 -3
- package/dist/integracoes-locais.js +17 -0
- package/dist/leases.js +65 -21
- package/dist/ledger-stats.js +272 -0
- package/dist/ledger.js +107 -2
- package/dist/licoes.js +215 -0
- package/dist/liveness.js +218 -0
- package/dist/maestro-actions.js +51 -0
- package/dist/maestro-authority.js +152 -0
- package/dist/maestro-cli.js +97 -0
- package/dist/maestro-contract.js +55 -0
- package/dist/maestro-discovery.js +132 -0
- package/dist/maestro-runtime.js +268 -0
- package/dist/maestro-snapshot.js +86 -0
- package/dist/maestro-sources.js +224 -0
- package/dist/manifest.js +165 -6
- package/dist/maquina.js +102 -0
- package/dist/master-audit.js +101 -0
- package/dist/master-batch.js +43 -0
- package/dist/master-digest.js +149 -0
- package/dist/master-migracao.js +155 -0
- package/dist/master.js +361 -77
- package/dist/mcp-artifacts.js +234 -0
- package/dist/mcp-git.js +433 -0
- package/dist/mcp-install.js +310 -0
- package/dist/mcp-maestro.js +25 -0
- package/dist/mcp-server.js +469 -0
- package/dist/mcp-ship.js +436 -0
- package/dist/mcp-verify.js +173 -0
- package/dist/memoria-humana.js +207 -0
- package/dist/memoria.js +314 -78
- package/dist/memory-migration.js +279 -0
- package/dist/memory-prospective.js +148 -0
- package/dist/modos-migracao.js +191 -0
- package/dist/modos.js +151 -15
- package/dist/monitor-lock.js +110 -0
- package/dist/objective.js +449 -0
- package/dist/ocupacao.js +267 -0
- package/dist/onboarding.js +303 -0
- package/dist/orkmind.js +389 -90
- package/dist/orquestracao.js +96 -69
- package/dist/phase.js +596 -150
- package/dist/playbook-capabilities.js +150 -0
- package/dist/playbook-contracts.js +188 -0
- package/dist/portfolio-context.js +61 -0
- package/dist/portfolio.js +180 -0
- package/dist/preflight.js +207 -0
- package/dist/process-audit.js +112 -0
- package/dist/project-state.js +93 -0
- package/dist/prompts.js +25 -5
- package/dist/prova-minima.js +112 -0
- package/dist/pulse-cadencia.js +163 -0
- package/dist/pulse-consentimento.js +260 -0
- package/dist/pulse-delivery.js +323 -0
- package/dist/pulse-resposta.js +542 -0
- package/dist/pulse.js +262 -0
- package/dist/ratelimit.js +35 -2
- package/dist/recall.js +80 -3
- package/dist/redacao-saida.js +45 -0
- package/dist/redacao-url.js +66 -0
- package/dist/retry.js +637 -115
- package/dist/roadmap-reservas.js +243 -0
- package/dist/runtime-ambiente.js +38 -0
- package/dist/runtime-context.js +215 -0
- package/dist/runtime-profiles.js +869 -0
- package/dist/runtimes.js +103 -0
- package/dist/sandbox.js +9 -0
- package/dist/session-events.js +203 -0
- package/dist/session-watcher-claude.js +637 -0
- package/dist/session-watcher.js +683 -0
- package/dist/sessoes-adopt.js +154 -0
- package/dist/sessoes-inventario.js +159 -0
- package/dist/sessoes.js +16 -35
- package/dist/setup.js +691 -0
- package/dist/ship.js +150 -15
- package/dist/slug.js +1 -1
- package/dist/thread-close.js +115 -0
- package/dist/thread.js +142 -28
- package/dist/tokens.js +1 -1
- package/dist/util.js +4 -2
- package/dist/verify-sandbox.js +265 -0
- package/dist/verify.js +270 -25
- package/dist/versao.js +60 -0
- package/dist/worktree.js +31 -2
- package/dist/write-activation.js +291 -0
- package/dist/yaml.js +2 -0
- package/eval/casos/onboarding.json +102 -0
- package/eval/casos/orkastery-bootstrap.json +35 -1
- package/eval/casos/scope-check-capability-map.json +45 -7
- package/eval/casos/ship-release.json +2 -2
- package/eval/fixtures/b0-slug-e-modos/caso.json +35 -22
- package/eval/fixtures/b2-master-log/caso.json +1 -1
- package/eval/fixtures/fx-adapter-editado-detectado/caso.json +19 -0
- package/eval/fixtures/fx-auto-quiet/caso.json +11 -0
- package/eval/fixtures/fx-blanket-approve/caso.json +10 -0
- package/eval/fixtures/fx-check-runtime-cruzado/caso.json +20 -0
- package/eval/fixtures/fx-codex-dry/caso.json +18 -0
- package/eval/fixtures/fx-donewhen-executavel/caso.json +15 -0
- package/eval/fixtures/fx-estado-dividido/caso.json +15 -0
- package/eval/fixtures/fx-fase-orfa/caso.json +116 -0
- package/eval/fixtures/fx-happy/caso.json +6 -4
- package/eval/fixtures/fx-hitl-latency/caso.json +20 -0
- package/eval/fixtures/fx-indice-reversao/caso.json +25 -0
- package/eval/fixtures/fx-listagem-abertas/caso.json +16 -0
- package/eval/fixtures/fx-maestro-bootstrap/caso.json +21 -0
- package/eval/fixtures/fx-modo-aposentado-escritor/caso.json +21 -0
- package/eval/fixtures/fx-modo-aposentado-leitor/caso.json +16 -0
- package/eval/fixtures/fx-objective-oscillation/caso.json +14 -0
- package/eval/fixtures/fx-omnicanal/caso.json +17 -0
- package/eval/fixtures/fx-sensores-runtime/caso.json +17 -0
- package/monitor/company-brain.cjs +17 -0
- package/monitor/pulse-scope.cjs +45 -0
- package/monitor/pulse.cron +18 -0
- package/monitor/varredura-pulse.sh +27 -0
- package/package.json +25 -4
- package/schemas/claims.schema.json +25 -0
- package/schemas/company-brain.schema.json +1158 -0
- package/schemas/creation-operation.schema.json +365 -0
- package/schemas/maestro-snapshot.schema.json +2716 -0
- package/skills/core/onboarding/SKILL.md +50 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +95 -65
- package/skills/core/thread-state/SKILL.md +3 -1
- package/skills/governance/scope-check-capability-map/SKILL.md +17 -7
|
@@ -8,39 +8,143 @@
|
|
|
8
8
|
* O que vale: `claude --bg "<prompt>" --name <slug>` rodando com cwd no diretorio do projeto,
|
|
9
9
|
* o que deixa a sessao visivel no app Claude e no `claude agents --json`.
|
|
10
10
|
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
11
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.NOME_ADAPTER = void 0;
|
|
45
|
+
exports.JANELA_CURTA_MAX_MS = exports.NOME_ADAPTER = void 0;
|
|
46
|
+
exports.ambienteDoPerfil = ambienteDoPerfil;
|
|
13
47
|
exports.disponivel = disponivel;
|
|
14
48
|
exports.versao = versao;
|
|
15
49
|
exports.montarComando = montarComando;
|
|
16
50
|
exports.extrairSessionId = extrairSessionId;
|
|
17
51
|
exports.listarSessoes = listarSessoes;
|
|
52
|
+
exports.consultarSessoes = consultarSessoes;
|
|
53
|
+
exports.consultarAgentesNativos = consultarAgentesNativos;
|
|
18
54
|
exports.acharSessao = acharSessao;
|
|
19
55
|
exports.despachar = despachar;
|
|
20
56
|
exports.parseRateLimit = parseRateLimit;
|
|
57
|
+
exports.naturezaDoLimite = naturezaDoLimite;
|
|
58
|
+
exports.parseFalhaDeConta = parseFalhaDeConta;
|
|
59
|
+
exports.conferirAuth = conferirAuth;
|
|
21
60
|
exports.comandoAttach = comandoAttach;
|
|
22
61
|
exports.limparAnsi = limparAnsi;
|
|
23
62
|
exports.logs = logs;
|
|
63
|
+
exports.logsDaSessao = logsDaSessao;
|
|
24
64
|
exports.parar = parar;
|
|
25
65
|
exports.ocupacaoDeContexto = ocupacaoDeContexto;
|
|
26
66
|
const util_1 = require("../util");
|
|
67
|
+
const runtime_ambiente_1 = require("../runtime-ambiente");
|
|
68
|
+
const path = __importStar(require("node:path"));
|
|
69
|
+
const runtime_context_1 = require("../runtime-context");
|
|
70
|
+
const runtime_profiles_1 = require("../runtime-profiles");
|
|
71
|
+
function exec(cmd, args, cwd, timeoutMs, ambiente = (0, runtime_ambiente_1.ambienteDeAssinatura)()) {
|
|
72
|
+
return (0, util_1.exec)(cmd, args, cwd, timeoutMs, ambiente);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* I-33 (D2): ambiente do filho com o `CLAUDE_CONFIG_DIR` do perfil sobre a assinatura
|
|
76
|
+
* higienizada (sem provider pago). Sem perfil, o ambiente do processo, como antes da I-33.
|
|
77
|
+
* A mesma conta despacha, reverifica, consulta, le logs e para a sessao.
|
|
78
|
+
*/
|
|
79
|
+
function ambienteDoPerfil(perfil) {
|
|
80
|
+
if (perfil && (perfil.runtime !== 'claude-bg' || !perfil.configDir))
|
|
81
|
+
throw new Error('runtime.profiles.invalid: perfil sem CLAUDE_CONFIG_DIR no despacho claude-bg');
|
|
82
|
+
return (0, runtime_profiles_1.ambienteComPerfil)((0, runtime_ambiente_1.ambienteDeAssinatura)(), perfil);
|
|
83
|
+
}
|
|
27
84
|
exports.NOME_ADAPTER = 'claude-bg';
|
|
85
|
+
/** Consultas do perfil interactive; gates e respostas humanas nunca recebem grant. */
|
|
86
|
+
const CONSULTAS_MCP = [
|
|
87
|
+
'mcp__orkastery__ork_thread_status',
|
|
88
|
+
'mcp__orkastery__ork_phase_list',
|
|
89
|
+
'mcp__orkastery__ork_hitl_pending',
|
|
90
|
+
'mcp__orkastery__ork_observe',
|
|
91
|
+
'mcp__orkastery__ork_artifact_read',
|
|
92
|
+
'mcp__orkastery__ork_claims_list',
|
|
93
|
+
];
|
|
94
|
+
const MUTACOES_WORKTREE = [
|
|
95
|
+
'mcp__orkastery__ork_artifact_write', 'mcp__orkastery__ork_claim_add',
|
|
96
|
+
'mcp__orkastery__ork_git_commit', 'mcp__orkastery__ork_verify', 'mcp__orkastery__ork_ship',
|
|
97
|
+
];
|
|
98
|
+
/** Regras nativas de arquivo, nao sandbox de processos. Edit tambem cobre Write. */
|
|
99
|
+
function permissoesDaWorktree(cwd, permiteEditarProduto) {
|
|
100
|
+
if (!path.isAbsolute(cwd) || cwd === '/' || path.normalize(cwd) !== cwd || !/^[A-Za-z0-9_./-]+$/.test(cwd))
|
|
101
|
+
throw Error('runtime.context.permissions: worktree contem caracteres ambiguos para regras nativas; use caminho sem glob, espacos ou delimitadores');
|
|
102
|
+
const absoluto = '/' + cwd; // Claude exige // para caminho absoluto.
|
|
103
|
+
const deny = [];
|
|
104
|
+
for (const nome of ['.git', '.orkastery', '.claude', '.codex', '.agents', '.env', '.env*']) {
|
|
105
|
+
for (const prefixo of [`${absoluto}/${nome}`, `${absoluto}/**/${nome}`])
|
|
106
|
+
deny.push(`Edit(${prefixo})`, `Edit(${prefixo}/**)`);
|
|
107
|
+
}
|
|
108
|
+
return { allow: [...CONSULTAS_MCP, 'mcp__orkastery__ork_git_status', ...MUTACOES_WORKTREE, ...(permiteEditarProduto ? [`Edit(${absoluto}/**)`] : [])], deny };
|
|
109
|
+
}
|
|
28
110
|
/** O runtime esta disponivel nesta maquina? */
|
|
29
111
|
function disponivel() {
|
|
30
112
|
return (0, util_1.noPath)('claude');
|
|
31
113
|
}
|
|
32
114
|
/** Versao do runtime, para o `ork doctor`. */
|
|
33
115
|
function versao() {
|
|
34
|
-
const r =
|
|
116
|
+
const r = exec('claude', ['--version'], undefined, 30000);
|
|
35
117
|
return r.ok ? r.stdout.trim() : null;
|
|
36
118
|
}
|
|
119
|
+
/** Ferramentas de arquivo negadas no PLAN: o plano sai pelo núcleo, não pelo disco. */
|
|
120
|
+
const ESCRITA_DE_ARQUIVO = ['Edit', 'Write', 'NotebookEdit'];
|
|
37
121
|
/** Monta a linha de comando exata do despacho (usada tambem pelo `--dry-run`). */
|
|
38
122
|
function montarComando(pedido) {
|
|
39
123
|
const args = ['--bg', pedido.prompt, '--name', pedido.nome];
|
|
124
|
+
// I-34 (D6): plan mode em `--bg` desabilita ExitPlanMode, grava o plano em ~/.claude/plans
|
|
125
|
+
// e bloqueia ork_artifact_write. O PLAN roda como sessão comum, sem escrita de arquivo e
|
|
126
|
+
// com o artefato gravado pelo núcleo; o agente `ork-plan` não lista ferramentas MCP.
|
|
127
|
+
const plano = pedido.colaboracao === 'plan';
|
|
40
128
|
if (pedido.model)
|
|
41
129
|
args.push('--model', pedido.model);
|
|
42
130
|
if (pedido.effort)
|
|
43
131
|
args.push('--effort', pedido.effort);
|
|
132
|
+
if (pedido.contextoRuntime) {
|
|
133
|
+
if (pedido.contextoRuntime.host !== 'claude-code')
|
|
134
|
+
throw Error('runtime.context.invalid: host Claude esperado');
|
|
135
|
+
const contexto = (0, runtime_context_1.validarContextoRuntime)(pedido.contextoRuntime, pedido.cwd);
|
|
136
|
+
// Somente o servidor gerado: nao mesclar MCP de outros escopos nesta sessao filha.
|
|
137
|
+
const permissoes = contexto.permissoesFilho === 'worktree'
|
|
138
|
+
? permissoesDaWorktree(pedido.cwd, contexto.permiteEditarProduto && !plano) : null;
|
|
139
|
+
// PLAN não implementa: nem commit, nem claim, nem verify, nem ship, em nenhum perfil.
|
|
140
|
+
const allow = plano ? [...CONSULTAS_MCP, 'mcp__orkastery__ork_artifact_write'] : permissoes?.allow ?? [...CONSULTAS_MCP];
|
|
141
|
+
const deny = [...(plano ? ESCRITA_DE_ARQUIVO : []), ...(permissoes?.deny ?? [])];
|
|
142
|
+
args.push('--plugin-dir', contexto.instalacao, '--strict-mcp-config', '--mcp-config', JSON.stringify({ mcpServers: { orkastery: contexto.servidor } }), '--allowedTools', allow.join(','));
|
|
143
|
+
if (deny.length)
|
|
144
|
+
args.push('--disallowedTools', deny.join(','));
|
|
145
|
+
}
|
|
146
|
+
else if (plano)
|
|
147
|
+
args.push('--disallowedTools', ESCRITA_DE_ARQUIVO.join(','));
|
|
44
148
|
return ['claude', ...args];
|
|
45
149
|
}
|
|
46
150
|
/**
|
|
@@ -61,25 +165,81 @@ function extrairSessionId(saida) {
|
|
|
61
165
|
* em 02/09/2026 na versao 2.1.259, `claude agents --json --cwd <raiz>` devolve lista
|
|
62
166
|
* vazia mesmo com sessoes vivas naquele diretorio.
|
|
63
167
|
*/
|
|
64
|
-
function listarSessoes(cwd, todas = false) {
|
|
168
|
+
function listarSessoes(cwd, todas = false, ambiente = (0, runtime_ambiente_1.ambienteDeAssinatura)()) {
|
|
169
|
+
return consultarSessoes(cwd, todas, ambiente).sessoes;
|
|
170
|
+
}
|
|
171
|
+
/** Falha de consulta não pode ser confundida com uma máquina sem sessões. */
|
|
172
|
+
function consultarSessoes(cwd, todas = false, ambiente = (0, runtime_ambiente_1.ambienteDeAssinatura)()) {
|
|
65
173
|
const args = ['agents', '--json'];
|
|
66
174
|
if (todas)
|
|
67
175
|
args.push('--all');
|
|
68
|
-
const r =
|
|
176
|
+
const r = exec('claude', args, undefined, 60000, ambiente);
|
|
69
177
|
if (!r.ok)
|
|
70
|
-
return [];
|
|
178
|
+
return { ok: false, sessoes: [], detalhe: `claude agents falhou (código ${r.code})` };
|
|
71
179
|
let dados;
|
|
72
180
|
try {
|
|
73
181
|
const bruto = JSON.parse(r.stdout);
|
|
74
|
-
|
|
182
|
+
if (!Array.isArray(bruto) || bruto.some(s => !s || typeof s !== 'object' ||
|
|
183
|
+
typeof s.sessionId !== 'string' || !/^[a-f0-9-]{8,}$/i.test(s.sessionId) ||
|
|
184
|
+
typeof s.cwd !== 'string' || !path.isAbsolute(s.cwd) ||
|
|
185
|
+
(s.state !== undefined && typeof s.state !== 'string') ||
|
|
186
|
+
(s.status !== undefined && typeof s.status !== 'string')) ||
|
|
187
|
+
new Set(bruto.map(s => s.sessionId)).size !== bruto.length)
|
|
188
|
+
throw new Error('formato inválido');
|
|
189
|
+
dados = bruto;
|
|
75
190
|
}
|
|
76
191
|
catch {
|
|
77
|
-
return [];
|
|
192
|
+
return { ok: false, sessoes: [], detalhe: 'claude agents respondeu JSON inválido' };
|
|
78
193
|
}
|
|
79
194
|
if (!cwd)
|
|
80
|
-
return dados;
|
|
195
|
+
return { ok: true, sessoes: dados, detalhe: '' };
|
|
81
196
|
const raiz = cwd.replace(/\/$/, '');
|
|
82
|
-
return dados.filter((s) => s.cwd === raiz || (s.cwd ?? '').startsWith(raiz + '/'));
|
|
197
|
+
return { ok: true, sessoes: dados.filter((s) => s.cwd === raiz || (s.cwd ?? '').startsWith(raiz + '/')), detalhe: '' };
|
|
198
|
+
}
|
|
199
|
+
const UUID_CLAUDE = /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i;
|
|
200
|
+
const LIMITE_CONSULTA_BYTES = 1024 * 1024;
|
|
201
|
+
/**
|
|
202
|
+
* I-34 (D5): fonte nativa da conclusão claude-bg. Sem `--cwd`: medido em 19/09/2026 na
|
|
203
|
+
* versão 2.1.278, `--cwd <worktree>` devolveu `[]` com a sessão viva ali. `--all` traz
|
|
204
|
+
* sessões de todos os projetos, então só `sessionId` UUID e `cwd` absoluto são exigidos por
|
|
205
|
+
* registro: campo de tipo inesperado vira ausente (estado ausente não conclui nada) e registro
|
|
206
|
+
* sem identidade é descartado. `sessionId` duplicado invalida a consulta inteira.
|
|
207
|
+
*/
|
|
208
|
+
function consultarAgentesNativos(agoraMs, ambiente = (0, runtime_ambiente_1.ambienteDeAssinatura)()) {
|
|
209
|
+
// I-33 (D4): o registro nativo e da conta; o observador passa o env do perfil da sessao.
|
|
210
|
+
const consultadoEm = new Date(agoraMs ?? Date.now()).toISOString();
|
|
211
|
+
const falha = (detalhe) => ({ ok: false, registros: [], detalhe, consultadoEm });
|
|
212
|
+
const r = exec('claude', ['agents', '--json', '--all'], undefined, 60000, ambiente);
|
|
213
|
+
if (!r.ok)
|
|
214
|
+
return falha(`claude agents falhou (código ${r.code})`);
|
|
215
|
+
if (Buffer.byteLength(r.stdout) > LIMITE_CONSULTA_BYTES)
|
|
216
|
+
return falha('claude agents excedeu 1 MiB');
|
|
217
|
+
const texto = (v) => typeof v === 'string' ? v : undefined;
|
|
218
|
+
let bruto;
|
|
219
|
+
try {
|
|
220
|
+
bruto = JSON.parse(r.stdout);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return falha('claude agents respondeu JSON inválido');
|
|
224
|
+
}
|
|
225
|
+
if (!Array.isArray(bruto))
|
|
226
|
+
return falha('claude agents respondeu JSON inválido');
|
|
227
|
+
const registros = [];
|
|
228
|
+
let descartados = 0;
|
|
229
|
+
for (const s of bruto) {
|
|
230
|
+
if (!s || typeof s !== 'object' || Array.isArray(s) || typeof s.sessionId !== 'string' || !UUID_CLAUDE.test(s.sessionId) ||
|
|
231
|
+
typeof s.cwd !== 'string' || !path.isAbsolute(s.cwd)) {
|
|
232
|
+
descartados++;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
registros.push({ sessionId: s.sessionId, cwd: s.cwd, id: texto(s.id), name: texto(s.name), kind: texto(s.kind),
|
|
236
|
+
state: texto(s.state), status: texto(s.status),
|
|
237
|
+
pid: Number.isSafeInteger(s.pid) && s.pid > 0 ? s.pid : undefined,
|
|
238
|
+
startedAt: typeof s.startedAt === 'number' && Number.isFinite(s.startedAt) ? s.startedAt : undefined });
|
|
239
|
+
}
|
|
240
|
+
if (new Set(registros.map(s => s.sessionId)).size !== registros.length)
|
|
241
|
+
return falha('claude agents repetiu sessionId');
|
|
242
|
+
return { ok: true, registros, detalhe: descartados ? `${descartados} registro(s) sem identidade descartado(s)` : '', consultadoEm };
|
|
83
243
|
}
|
|
84
244
|
/** Procura uma sessao pelo id curto, id completo ou nome. */
|
|
85
245
|
function acharSessao(chave, sessoes) {
|
|
@@ -94,6 +254,13 @@ function acharSessao(chave, sessoes) {
|
|
|
94
254
|
*/
|
|
95
255
|
function despachar(pedido) {
|
|
96
256
|
const comando = montarComando(pedido);
|
|
257
|
+
let ambiente;
|
|
258
|
+
try {
|
|
259
|
+
ambiente = { ...ambienteDoPerfil(pedido.perfil), ...(pedido.ambienteExtra ?? {}) };
|
|
260
|
+
}
|
|
261
|
+
catch (e) {
|
|
262
|
+
return { ok: false, comando, sessionId: null, verificada: false, stdout: '', stderr: '', erro: e.message };
|
|
263
|
+
}
|
|
97
264
|
if (pedido.dryRun) {
|
|
98
265
|
return { ok: true, comando, sessionId: null, verificada: false, stdout: '', stderr: '' };
|
|
99
266
|
}
|
|
@@ -108,13 +275,19 @@ function despachar(pedido) {
|
|
|
108
275
|
erro: 'binario `claude` nao encontrado no PATH',
|
|
109
276
|
};
|
|
110
277
|
}
|
|
111
|
-
const r =
|
|
278
|
+
const r = exec(comando[0], comando.slice(1), pedido.cwd, 180000, ambiente);
|
|
112
279
|
const saida = r.stdout + '\n' + r.stderr;
|
|
113
280
|
const sessionId = extrairSessionId(saida);
|
|
114
281
|
if (!r.ok || !sessionId) {
|
|
115
282
|
// Bloco B3: rate limit nao e "runtime indisponivel". Ele tem horario de reset, e por
|
|
116
283
|
// isso volta tipado daqui para virar pedido na fila duravel em vez de falha seca.
|
|
117
|
-
|
|
284
|
+
// I-33 (D1): falha da CONTA vem antes, porque e mais especifica e pede outro perfil.
|
|
285
|
+
// N6 (P8): sem perfil nada muda; o esgotamento que a leitura da baseline ja lia como rate limit
|
|
286
|
+
// ("Claude AI usage limit reached|<epoch>") segue direto para a fila do B3, como antes da I-33.
|
|
287
|
+
const lida = parseFalhaDeConta(saida);
|
|
288
|
+
const legado = parseRateLimit(saida);
|
|
289
|
+
const falhaDeConta = lida?.motivo === 'runtime.quota-exhausted' && !pedido.perfil && legado ? null : lida;
|
|
290
|
+
const rateLimit = falhaDeConta ? null : legado;
|
|
118
291
|
return {
|
|
119
292
|
ok: false,
|
|
120
293
|
comando,
|
|
@@ -123,13 +296,18 @@ function despachar(pedido) {
|
|
|
123
296
|
stdout: r.stdout,
|
|
124
297
|
stderr: r.stderr,
|
|
125
298
|
rateLimit,
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
:
|
|
299
|
+
falhaDeConta,
|
|
300
|
+
erro: falhaDeConta
|
|
301
|
+
? `${falhaDeConta.motivo}: ${falhaDeConta.trecho}`
|
|
302
|
+
: rateLimit
|
|
303
|
+
? `limite de uso do runtime atingido: ${rateLimit.trecho}`
|
|
304
|
+
: `despacho falhou (code ${r.code})`,
|
|
129
305
|
};
|
|
130
306
|
}
|
|
131
|
-
|
|
132
|
-
const
|
|
307
|
+
// I-33 (D4): a reverificacao consulta a MESMA conta que despachou.
|
|
308
|
+
const sessoes = listarSessoes(undefined, true, ambiente);
|
|
309
|
+
const achada = acharSessao(sessionId, sessoes);
|
|
310
|
+
const verificada = achada !== null || acharSessao(pedido.nome, sessoes) !== null;
|
|
133
311
|
return {
|
|
134
312
|
ok: true,
|
|
135
313
|
comando,
|
|
@@ -177,31 +355,75 @@ function casarIso(texto) {
|
|
|
177
355
|
return null;
|
|
178
356
|
return { resetEm: data.toISOString(), trecho: m[0].trim() };
|
|
179
357
|
}
|
|
358
|
+
/** N5: uma parte da duracao (`4 days`, `3h`, `20m`, `1.5s`, `20ms`), com a unidade fechada sem exigir `\b`. */
|
|
359
|
+
const PARTE_DA_DURACAO = /(\d+(?:\.\d+)?)\s*(milliseconds?|ms|days?|d|hours?|hrs?|h|minutes?|mins?|m|seconds?|secs?|s)(?![a-z])/gi;
|
|
360
|
+
function segundosDaUnidade(unidade) {
|
|
361
|
+
const u = unidade.toLowerCase();
|
|
362
|
+
if (u === 'ms' || u.startsWith('milli'))
|
|
363
|
+
return 0.001;
|
|
364
|
+
if (u.startsWith('d'))
|
|
365
|
+
return 86400;
|
|
366
|
+
if (u.startsWith('h'))
|
|
367
|
+
return 3600;
|
|
368
|
+
if (u.startsWith('m'))
|
|
369
|
+
return 60;
|
|
370
|
+
return 1;
|
|
371
|
+
}
|
|
180
372
|
function casarDuracao(texto, agoraMs) {
|
|
181
373
|
const m = texto.match(
|
|
182
|
-
// A unidade aceita plural (`25 minutes`)
|
|
183
|
-
//
|
|
184
|
-
/(?:try again|retry|reset[a-z]*|available|wait)[^\n]{0,20}?\bin\b\s*(
|
|
374
|
+
// A unidade aceita plural (`25 minutes`) e, desde o N5, dias e partes compostas nas formas reais
|
|
375
|
+
// dos runtimes: `4 days 3 hours`, `2d 5h 30m` (Claude Code), `1m30s` e `20ms` (API da OpenAI).
|
|
376
|
+
/(?:try again|retry|reset[a-z]*|available|wait)[^\n]{0,20}?\bin\b\s*((?:\d+(?:\.\d+)?\s*(?:milliseconds?|ms|days?|d|hours?|hrs?|h|minutes?|mins?|m|seconds?|secs?|s)(?![a-z])[\s,]*(?:and\s+)?)+)|retry[- ]after[:\s]+(\d+)/i);
|
|
185
377
|
if (!m)
|
|
186
378
|
return null;
|
|
187
|
-
if (m[
|
|
379
|
+
if (m[2] !== undefined) {
|
|
188
380
|
// `Retry-After: 3600` e sempre em segundos.
|
|
189
381
|
return {
|
|
190
|
-
resetEm: new Date(agoraMs + Number(m[
|
|
382
|
+
resetEm: new Date(agoraMs + Number(m[2]) * 1000).toISOString(),
|
|
191
383
|
trecho: m[0].trim(),
|
|
192
384
|
};
|
|
193
385
|
}
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
386
|
+
let segundos = 0;
|
|
387
|
+
for (const parte of m[1].matchAll(PARTE_DA_DURACAO))
|
|
388
|
+
segundos += Number(parte[1]) * segundosDaUnidade(parte[2]);
|
|
389
|
+
return { resetEm: new Date(agoraMs + Math.round(segundos * 1000)).toISOString(), trecho: m[0].trim().replace(/[\s,]+$/, '') };
|
|
390
|
+
}
|
|
391
|
+
const MESES = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
|
|
392
|
+
/**
|
|
393
|
+
* N5: data e hora de relogio local nas formas reais. Codex: `or try again at Sep 22nd, 2026 3:05 PM`
|
|
394
|
+
* (`%b %-d` com sufixo ordinal, `, %Y %-I:%M %p`). Claude Code, quando o reset passa de 24 h:
|
|
395
|
+
* `resets Sep 22, 3pm (<nome IANA do fuso>)` e, em outro ano, `resets Sep 22, 2027, 3:05pm`. Sem ano,
|
|
396
|
+
* vale o ano corrente, ou o seguinte quando a data ja ficou mais de um dia para tras. O fuso entre
|
|
397
|
+
* parenteses nao entra na regex nem no calculo (a hora e lida no relogio desta maquina), e por isso
|
|
398
|
+
* fica como placeholder: o nome literal de fuso mora so em `core/src/horario.ts` (I-35).
|
|
399
|
+
*/
|
|
400
|
+
function casarData(texto, agoraMs) {
|
|
401
|
+
const m = texto.match(/(?:try again at|reset[a-z]*(?:\s+at)?)\s+(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\.?\s+(\d{1,2})(?:st|nd|rd|th)?,?\s*(?:(\d{4}),?\s*)?(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*([ap]\.?m\.?)?(?![a-z0-9])/i);
|
|
402
|
+
if (!m)
|
|
403
|
+
return null;
|
|
404
|
+
const mes = MESES.indexOf(m[1].toLowerCase());
|
|
405
|
+
const dia = Number(m[2]);
|
|
406
|
+
let hora = Number(m[4]);
|
|
407
|
+
const minuto = Number(m[5] ?? '0');
|
|
408
|
+
const sufixo = (m[6] ?? '').toLowerCase().replace(/\./g, '');
|
|
409
|
+
if (sufixo === 'pm' && hora < 12)
|
|
410
|
+
hora += 12;
|
|
411
|
+
if (sufixo === 'am' && hora === 12)
|
|
412
|
+
hora = 0;
|
|
413
|
+
if (dia < 1 || dia > 31 || hora > 23 || minuto > 59)
|
|
414
|
+
return null;
|
|
415
|
+
const base = new Date(agoraMs);
|
|
416
|
+
const ano = m[3] !== undefined ? Number(m[3]) : base.getFullYear();
|
|
417
|
+
const alvo = new Date(ano, mes, dia, hora, minuto, 0, 0);
|
|
418
|
+
if (alvo.getMonth() !== mes)
|
|
419
|
+
return null;
|
|
420
|
+
if (m[3] === undefined && alvo.getTime() < agoraMs - 24 * 3600 * 1000)
|
|
421
|
+
alvo.setFullYear(ano + 1);
|
|
422
|
+
return { resetEm: alvo.toISOString(), trecho: m[0].trim() };
|
|
202
423
|
}
|
|
203
424
|
function casarRelogio(texto, agoraMs) {
|
|
204
|
-
|
|
425
|
+
// N5: o codex diz so a hora quando o reset e no mesmo dia (`or try again at 3:05 PM`).
|
|
426
|
+
const m = texto.match(/(?:reset[a-z]*\s*(?:at|em|as)?|try again at)\s*(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\b/i);
|
|
205
427
|
if (!m)
|
|
206
428
|
return null;
|
|
207
429
|
let hora = Number(m[1]);
|
|
@@ -239,6 +461,9 @@ function parseRateLimit(saida, agoraMs = Date.now()) {
|
|
|
239
461
|
const iso = casarIso(texto);
|
|
240
462
|
if (iso)
|
|
241
463
|
return { resetEm: iso.resetEm, fonte: 'iso', trecho: iso.trecho };
|
|
464
|
+
const data = casarData(texto, agoraMs);
|
|
465
|
+
if (data)
|
|
466
|
+
return { resetEm: data.resetEm, fonte: 'relogio', trecho: data.trecho };
|
|
242
467
|
const duracao = casarDuracao(texto, agoraMs);
|
|
243
468
|
if (duracao)
|
|
244
469
|
return { resetEm: duracao.resetEm, fonte: 'duracao', trecho: duracao.trecho };
|
|
@@ -248,6 +473,178 @@ function parseRateLimit(saida, agoraMs = Date.now()) {
|
|
|
248
473
|
const linha = texto.split('\n').find((l) => frase.test(l)) ?? texto;
|
|
249
474
|
return { resetEm: null, fonte: 'sem-horario', trecho: linha.trim().slice(0, 200) };
|
|
250
475
|
}
|
|
476
|
+
/**
|
|
477
|
+
* I-33 (D1): falha da CONTA na saida real do runtime, reconhecida por frase, nunca por codigo de
|
|
478
|
+
* saida. Auth ausente vem primeiro: sem login, nada mais da saida importa. Cota esgotada usa os
|
|
479
|
+
* mesmos padroes de horario do rate limit; sem horario, `resetEm` fica `null` e o prazo e da
|
|
480
|
+
* janela padrao do manifesto, declarado como estimado por quem marca o perfil. O que e cota
|
|
481
|
+
* esgotada e o que e rate limit comum sai do criterio unico da D16 (`naturezaDoLimite`).
|
|
482
|
+
*/
|
|
483
|
+
const FRASES_DE_AUTH_AUSENTE = [
|
|
484
|
+
/\bnot logged in\b/i,
|
|
485
|
+
/please run \/login/i,
|
|
486
|
+
/oauth token (?:has )?expired/i,
|
|
487
|
+
/\bauthentication_error\b/i,
|
|
488
|
+
// D15 (A6): o erro real do Claude Code quando o refresh do OAuth falha (codigo e frase).
|
|
489
|
+
/\bauthentication_failed\b/i,
|
|
490
|
+
/oauth session expired/i,
|
|
491
|
+
/\btoken_expired\b/i,
|
|
492
|
+
/refresh[_ ]token[^\n]{0,40}\b(?:expired|revoked|invalid|reused)\b/i,
|
|
493
|
+
/\b401\b[^\n]{0,40}unauthori[sz]ed|unauthori[sz]ed[^\n]{0,40}\b401\b/i,
|
|
494
|
+
];
|
|
495
|
+
/**
|
|
496
|
+
* A11: mencao ampla ao comando de login do codex. Aparece tambem em dica e em texto de ajuda, entao
|
|
497
|
+
* so vale como auth ausente quando o mesmo texto nao traz frase de cota: a cota, mais especifica, vence.
|
|
498
|
+
*/
|
|
499
|
+
const FRASES_AMPLAS_DE_AUTH = [/\bcodex login\b/i];
|
|
500
|
+
/**
|
|
501
|
+
* I-33 (D16): CRITERIO UNICO entre ESGOTAMENTO da conta e RATE LIMIT comum. Decisao do dono em
|
|
502
|
+
* 19/09/2026 (opcao a do A3 do CHECK aa279e17): esgotamento de cota, credito ou limite do plano
|
|
503
|
+
* tira o perfil do rodizio e o MESMO prompt pode seguir no proximo perfil ativo do mesmo runtime;
|
|
504
|
+
* rate limit comum de curta janela NUNCA troca de perfil, espera a janela. Todo classificador de
|
|
505
|
+
* falha da conta passa por aqui: despacho claude-bg e codex, transcricao do Claude Code e erro
|
|
506
|
+
* terminal do turno codex (`parseFalhaDeConta`). Regras, na ordem, pela frase do runtime:
|
|
507
|
+
*
|
|
508
|
+
* 1. `esgotamento-explicito`: o provedor diz que a conta ficou sem cota, credito ou saldo
|
|
509
|
+
* (`usage_limit_exceeded`, `insufficient_quota`, "out of credits", "spend cap"). Esgotamento,
|
|
510
|
+
* com ou sem prazo, mesmo que a resposta venha como HTTP 429.
|
|
511
|
+
* 2. `janela-curta`: o provedor diz que o limite e temporario e nao e a cota ("not your usage
|
|
512
|
+
* limit", "Request rejected (429)", "Too Many Requests", `rate_limit_exceeded`,
|
|
513
|
+
* `rate_limit_error`, overloaded, 529, requisicoes ou tokens por minuto). Rate limit.
|
|
514
|
+
* 3. `limite-do-plano`: frase da janela da assinatura ("usage limit reached", "You've hit your
|
|
515
|
+
* ... limit", "You've reached your ... limit", "plan limit"). Esgotamento quando o prazo dito
|
|
516
|
+
* esta a `JANELA_CURTA_MAX_MS` ou mais, ou quando o runtime nao diz prazo futuro (a janela do
|
|
517
|
+
* plano e de horas ou dias por construcao); rate limit quando o prazo dito e mais curto.
|
|
518
|
+
* 4. `generico`: qualquer outro sinal de limite reconhecido por `parseRateLimit` (`429`, "rate
|
|
519
|
+
* limit", `rate_limit`, "too many requests", "quota exceeded"). Rate limit: na duvida, nao troca
|
|
520
|
+
* de perfil. O Retry-After so conta junto de um desses sinais, como prazo.
|
|
521
|
+
*/
|
|
522
|
+
exports.JANELA_CURTA_MAX_MS = 15 * 60 * 1000;
|
|
523
|
+
const FRASES_DE_ESGOTAMENTO = [
|
|
524
|
+
/\busage_limit_exceeded\b/i,
|
|
525
|
+
/\busage_limit_reached\b/i,
|
|
526
|
+
/\bout of credits\b/i,
|
|
527
|
+
/\binsufficient_quota\b/i,
|
|
528
|
+
/exceeded your current quota/i,
|
|
529
|
+
/credit balance is too low/i,
|
|
530
|
+
// Codigo de erro de API do Claude Code quando a conta fica sem credito (I-33, D12).
|
|
531
|
+
/\bbilling_error\b/i,
|
|
532
|
+
/\bspend (?:cap|limit)\b/i,
|
|
533
|
+
/\bworkspace credit limit\b/i,
|
|
534
|
+
/\bout of extra usage\b/i,
|
|
535
|
+
/\brequires usage credits\b/i,
|
|
536
|
+
];
|
|
537
|
+
const FRASES_DE_JANELA_CURTA = [
|
|
538
|
+
/not your usage limit/i,
|
|
539
|
+
/temporarily limiting requests/i,
|
|
540
|
+
/request rejected \(429\)/i,
|
|
541
|
+
/temporary capacity issue/i,
|
|
542
|
+
/too many requests/i,
|
|
543
|
+
/\brate_limit_exceeded\b/i,
|
|
544
|
+
/\brate_limit_error\b/i,
|
|
545
|
+
/\bserver_overloaded\b/i,
|
|
546
|
+
/\boverloaded(?:_error)?\b/i,
|
|
547
|
+
/\b529\b/,
|
|
548
|
+
/experiencing high load/i,
|
|
549
|
+
/\b(?:requests|tokens) per min(?:ute)?\b/i,
|
|
550
|
+
];
|
|
551
|
+
const FRASES_DO_LIMITE_DO_PLANO = [
|
|
552
|
+
/usage limits? (?:reached|exceeded)/i,
|
|
553
|
+
/\bhit your (?:[a-z0-9-]+ )?(?:usage )?limit\b/i,
|
|
554
|
+
/\breached your (?:[a-z0-9-]+ )?(?:usage )?limit\b/i,
|
|
555
|
+
/\bplan limit\b/i,
|
|
556
|
+
/out of (?:usage|weekly|session) limit/i,
|
|
557
|
+
];
|
|
558
|
+
/** N4: a linha da frase, limitada a 200 caracteres; em linha longa, a janela comeca perto da frase. */
|
|
559
|
+
function trechoDa(texto, frase) {
|
|
560
|
+
const linha = (texto.split('\n').find((l) => frase.test(l)) ?? texto).trim();
|
|
561
|
+
if (linha.length <= 200)
|
|
562
|
+
return linha;
|
|
563
|
+
const inicio = Math.max(0, (linha.match(frase)?.index ?? 0) - 40);
|
|
564
|
+
return linha.slice(inicio, inicio + 200).trim();
|
|
565
|
+
}
|
|
566
|
+
/** Horario de volta dito pelo runtime, do padrao mais preciso para o menos; nenhum inventado. */
|
|
567
|
+
function resetDito(texto, agoraMs) {
|
|
568
|
+
// N5: data e hora de relogio local (`Sep 22nd, 2026 3:05 PM`) e relogio; a fonte das duas e `relogio`.
|
|
569
|
+
const padroes = [['epoch', casarEpoch(texto)], ['iso', casarIso(texto)], ['relogio', casarData(texto, agoraMs)],
|
|
570
|
+
['duracao', casarDuracao(texto, agoraMs)], ['relogio', casarRelogio(texto, agoraMs)]];
|
|
571
|
+
const achado = padroes.find(([, r]) => r !== null);
|
|
572
|
+
return achado ? { resetEm: achado[1].resetEm, fonte: achado[0] } : { resetEm: null, fonte: 'sem-horario' };
|
|
573
|
+
}
|
|
574
|
+
/** D16: a natureza do limite na saida do runtime, pelas quatro regras acima; `null` sem sinal de limite. */
|
|
575
|
+
function naturezaDoLimite(saida, agoraMs = Date.now()) {
|
|
576
|
+
const texto = saida ?? '';
|
|
577
|
+
const explicito = FRASES_DE_ESGOTAMENTO.find((r) => r.test(texto));
|
|
578
|
+
if (explicito)
|
|
579
|
+
return { natureza: 'esgotamento', regra: 'esgotamento-explicito', ...resetDito(texto, agoraMs), trecho: trechoDa(texto, explicito) };
|
|
580
|
+
const curta = FRASES_DE_JANELA_CURTA.find((r) => r.test(texto));
|
|
581
|
+
if (curta)
|
|
582
|
+
return { natureza: 'rate-limit', regra: 'janela-curta', ...resetDito(texto, agoraMs), trecho: trechoDa(texto, curta) };
|
|
583
|
+
const plano = FRASES_DO_LIMITE_DO_PLANO.find((r) => r.test(texto));
|
|
584
|
+
if (plano) {
|
|
585
|
+
const reset = resetDito(texto, agoraMs);
|
|
586
|
+
const horizonte = reset.resetEm === null ? null : Date.parse(reset.resetEm) - agoraMs;
|
|
587
|
+
const curto = horizonte !== null && horizonte > 0 && horizonte < exports.JANELA_CURTA_MAX_MS;
|
|
588
|
+
return { natureza: curto ? 'rate-limit' : 'esgotamento', regra: 'limite-do-plano', ...reset, trecho: trechoDa(texto, plano) };
|
|
589
|
+
}
|
|
590
|
+
const generico = parseRateLimit(texto, agoraMs);
|
|
591
|
+
return generico ? { natureza: 'rate-limit', regra: 'generico', resetEm: generico.resetEm, fonte: generico.fonte, trecho: generico.trecho }
|
|
592
|
+
: null;
|
|
593
|
+
}
|
|
594
|
+
function parseFalhaDeConta(saida, agoraMs = Date.now()) {
|
|
595
|
+
const texto = saida ?? '';
|
|
596
|
+
const auth = FRASES_DE_AUTH_AUSENTE.find((r) => r.test(texto));
|
|
597
|
+
if (auth)
|
|
598
|
+
return { motivo: 'runtime.auth-missing', resetEm: null, fonte: 'sem-horario', trecho: trechoDa(texto, auth) };
|
|
599
|
+
const limite = naturezaDoLimite(texto, agoraMs);
|
|
600
|
+
if (limite?.natureza === 'esgotamento') {
|
|
601
|
+
return { motivo: 'runtime.quota-exhausted', resetEm: limite.resetEm, fonte: limite.fonte, trecho: limite.trecho };
|
|
602
|
+
}
|
|
603
|
+
const ampla = FRASES_AMPLAS_DE_AUTH.find((r) => r.test(texto));
|
|
604
|
+
return ampla ? { motivo: 'runtime.auth-missing', resetEm: null, fonte: 'sem-horario', trecho: trechoDa(texto, ampla) } : null;
|
|
605
|
+
}
|
|
606
|
+
/** Campo textual do `auth status`, sem controle e curto, para o detalhe legivel. */
|
|
607
|
+
const campoDeAuth = (v) => typeof v === 'string' ? v.replace(/[^a-zA-Z0-9_.-]/g, '').slice(0, 32) : '';
|
|
608
|
+
/**
|
|
609
|
+
* I-33 (D7, D13): `claude auth status --json` com o env do perfil. Aprova so login de ASSINATURA:
|
|
610
|
+
* `loggedIn: true`, `authMethod` `claude.ai`, sem `apiKeySource` e com provider `firstParty`.
|
|
611
|
+
* API key, `api_key_helper`, Console ou provider de nuvem cobram por token: `pago`, nunca despacha.
|
|
612
|
+
* O `configDirectory` informado precisa ser o do perfil, prova de que o env foi aplicado. O `ork`
|
|
613
|
+
* nunca abre nada dentro do diretorio: quem responde e o CLI.
|
|
614
|
+
*/
|
|
615
|
+
function conferirAuth(perfil) {
|
|
616
|
+
let ambiente;
|
|
617
|
+
try {
|
|
618
|
+
ambiente = ambienteDoPerfil(perfil);
|
|
619
|
+
}
|
|
620
|
+
catch (e) {
|
|
621
|
+
return { ok: false, detalhe: e.message };
|
|
622
|
+
}
|
|
623
|
+
const r = exec('claude', ['auth', 'status', '--json'], undefined, 30000, ambiente);
|
|
624
|
+
// A14: sem codigo de saida (timeout, sinal ou binario ausente) ou sem JSON, a conferencia e inconclusiva.
|
|
625
|
+
if (r.code === -1)
|
|
626
|
+
return { ok: false, transitorio: true, detalhe: 'claude auth status sem resposta (timeout, sinal ou binario ausente)' };
|
|
627
|
+
let dados;
|
|
628
|
+
try {
|
|
629
|
+
dados = JSON.parse(r.stdout);
|
|
630
|
+
}
|
|
631
|
+
catch {
|
|
632
|
+
return { ok: false, transitorio: true, detalhe: `claude auth status sem JSON legivel (codigo ${r.code})` };
|
|
633
|
+
}
|
|
634
|
+
if (!dados || typeof dados !== 'object')
|
|
635
|
+
return { ok: false, transitorio: true, detalhe: 'claude auth status sem JSON legivel' };
|
|
636
|
+
if (perfil && typeof dados.configDirectory === 'string' && dados.configDirectory !== perfil.configDir)
|
|
637
|
+
return { ok: false, detalhe: 'claude auth status respondeu por outro diretorio de configuracao; perfil nao aplicado' };
|
|
638
|
+
if (dados.loggedIn !== true)
|
|
639
|
+
return { ok: false, detalhe: 'claude auth status: loggedIn false' };
|
|
640
|
+
const metodo = campoDeAuth(dados.authMethod), fonte = campoDeAuth(dados.apiKeySource), provedor = campoDeAuth(dados.apiProvider);
|
|
641
|
+
if (metodo !== 'claude.ai' || fonte !== '' || (provedor !== '' && provedor !== 'firstParty')) {
|
|
642
|
+
const partes = [`authMethod ${metodo || 'ausente'}`, ...(fonte ? [`apiKeySource ${fonte}`] : []),
|
|
643
|
+
...(provedor && provedor !== 'firstParty' ? [`apiProvider ${provedor}`] : [])];
|
|
644
|
+
return { ok: false, pago: true, detalhe: `claude auth status: provider pago (${partes.join(', ')}); so a assinatura claude.ai despacha` };
|
|
645
|
+
}
|
|
646
|
+
return { ok: true, detalhe: `claude auth status: loggedIn (${metodo})` };
|
|
647
|
+
}
|
|
251
648
|
/** Comando de anexar a sessao (precisa de TTY, entao o CLI imprime em vez de executar). */
|
|
252
649
|
function comandoAttach(sessionId) {
|
|
253
650
|
return `claude attach ${sessionId}`;
|
|
@@ -259,13 +656,58 @@ function comandoAttach(sessionId) {
|
|
|
259
656
|
* nao por quebra de linha. Por isso o movimento vira quebra ANTES de as demais
|
|
260
657
|
* sequencias ANSI serem removidas, senao a saida inteira colapsa numa linha so.
|
|
261
658
|
*/
|
|
659
|
+
/**
|
|
660
|
+
* Marcador temporario de um CHA (`ESC[<n>G`), resolvido em espacos no fim da limpeza.
|
|
661
|
+
*
|
|
662
|
+
* Fica fora da faixa de controle que a limpeza apaga (`\u0000-\u001f`), entao ele
|
|
663
|
+
* atravessa os passos seguintes intacto.
|
|
664
|
+
*/
|
|
665
|
+
const MARCA_DE_COLUNA = '\u0091';
|
|
666
|
+
const MARCA_CASADA = new RegExp(`${MARCA_DE_COLUNA}(\\d+)${MARCA_DE_COLUNA}`, 'g');
|
|
667
|
+
/**
|
|
668
|
+
* Repoe os espacos que o CHA desenhava, linha a linha.
|
|
669
|
+
*
|
|
670
|
+
* O terminal anda ate a coluna pedida preenchendo com branco; quem le o log so ve a
|
|
671
|
+
* sequencia sumir. Quando a coluna alvo ja passou (o runtime reescrevendo o que estava
|
|
672
|
+
* na tela), um unico espaco separa os pedacos: e melhor errar o alinhamento do que
|
|
673
|
+
* colar duas palavras que nunca foram uma so.
|
|
674
|
+
*/
|
|
675
|
+
function resolverColunas(texto) {
|
|
676
|
+
if (!texto.includes(MARCA_DE_COLUNA))
|
|
677
|
+
return texto;
|
|
678
|
+
return texto
|
|
679
|
+
.split('\n')
|
|
680
|
+
.map((linha) => {
|
|
681
|
+
// `split` com grupo de captura intercala texto e coluna: [txt, col, txt, col, txt].
|
|
682
|
+
const partes = linha.split(MARCA_CASADA);
|
|
683
|
+
let saida = partes[0] ?? '';
|
|
684
|
+
for (let i = 1; i < partes.length; i += 2) {
|
|
685
|
+
const coluna = Number(partes[i]);
|
|
686
|
+
if (Number.isFinite(coluna) && coluna - 1 > saida.length) {
|
|
687
|
+
saida += ' '.repeat(coluna - 1 - saida.length);
|
|
688
|
+
}
|
|
689
|
+
else if (saida !== '' && !saida.endsWith(' ')) {
|
|
690
|
+
saida += ' ';
|
|
691
|
+
}
|
|
692
|
+
saida += partes[i + 1] ?? '';
|
|
693
|
+
}
|
|
694
|
+
return saida;
|
|
695
|
+
})
|
|
696
|
+
.join('\n');
|
|
697
|
+
}
|
|
262
698
|
function limparAnsi(bruto) {
|
|
263
|
-
return bruto
|
|
699
|
+
return resolverColunas(bruto
|
|
264
700
|
.replace(/\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)/g, '')
|
|
265
701
|
.replace(/\u001b\[[0-9;]*[HfABEd]/g, '\n')
|
|
702
|
+
// O CHA (`ESC[<n>G`) posiciona o cursor numa coluna em vez de escrever espacos, e
|
|
703
|
+
// o `claude logs` desenha a tela INTEIRA assim. Engoli-lo junto com o resto das
|
|
704
|
+
// sequencias colava as palavras (`2newMCPserversfoundinthisproject`), o que
|
|
705
|
+
// apagava a pergunta que o radar poe na frente do humano e matava todo o
|
|
706
|
+
// reconhecimento por frase que separa credencial de permissao.
|
|
707
|
+
.replace(/\u001b\[([0-9]*)G/g, (_m, n) => `${MARCA_DE_COLUNA}${n || '1'}${MARCA_DE_COLUNA}`)
|
|
266
708
|
.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g, '')
|
|
267
709
|
.replace(/\u001b[()][A-Za-z0-9]/g, '')
|
|
268
|
-
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, '')
|
|
710
|
+
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, ''))
|
|
269
711
|
.replace(/[ \t]+$/gm, '')
|
|
270
712
|
.replace(/\n{3,}/g, '\n\n');
|
|
271
713
|
}
|
|
@@ -276,19 +718,26 @@ function limparAnsi(bruto) {
|
|
|
276
718
|
* nao o UUID completo: passar o UUID responde "No job matching". Por isso a chave e
|
|
277
719
|
* resolvida antes, e a saida (dump de tela com ANSI) e limpa aqui.
|
|
278
720
|
*/
|
|
279
|
-
function logs(chave, linhas = 60) {
|
|
280
|
-
const sessao = acharSessao(chave);
|
|
281
|
-
|
|
282
|
-
|
|
721
|
+
function logs(chave, linhas = 60, ambiente = (0, runtime_ambiente_1.ambienteDeAssinatura)()) {
|
|
722
|
+
const sessao = acharSessao(chave, listarSessoes(undefined, true, ambiente));
|
|
723
|
+
return logsDaSessao(sessao?.id ?? (sessao?.sessionId ?? chave).slice(0, 8), linhas, ambiente);
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* I-45: a tela de uma sessao cujo id curto o chamador ja tem. `logs` lista todas as sessoes antes
|
|
727
|
+
* de ler (`claude agents --json --all`, 57 s nesta VPS em 25/09/2026) so para achar esse id; o radar
|
|
728
|
+
* ja tem a lista na mao, e pagar a listagem de novo por sessao parada era o grosso da varredura.
|
|
729
|
+
*/
|
|
730
|
+
function logsDaSessao(id, linhas = 60, ambiente = (0, runtime_ambiente_1.ambienteDeAssinatura)()) {
|
|
731
|
+
const r = exec('claude', ['logs', id], undefined, 60000, ambiente);
|
|
283
732
|
const bruto = limparAnsi(r.ok ? r.stdout : r.stdout + r.stderr);
|
|
284
733
|
const uteis = bruto.split('\n').filter((l) => l.trim() !== '');
|
|
285
734
|
return { ok: r.ok, texto: uteis.slice(-linhas).join('\n') };
|
|
286
735
|
}
|
|
287
736
|
/** Para uma sessao em background (o `claude stop` tambem trabalha com o id curto). */
|
|
288
|
-
function parar(chave) {
|
|
289
|
-
const sessao = acharSessao(chave);
|
|
737
|
+
function parar(chave, ambiente = (0, runtime_ambiente_1.ambienteDeAssinatura)()) {
|
|
738
|
+
const sessao = acharSessao(chave, listarSessoes(undefined, true, ambiente));
|
|
290
739
|
const id = sessao?.id ?? (sessao?.sessionId ?? chave).slice(0, 8);
|
|
291
|
-
const r =
|
|
740
|
+
const r = exec('claude', ['stop', id], undefined, 60000, ambiente);
|
|
292
741
|
return { ok: r.ok, texto: limparAnsi(r.stdout + r.stderr).trim() };
|
|
293
742
|
}
|
|
294
743
|
/**
|