terminal-smart-cli 0.69.0 → 0.74.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/bin/ts.js +159 -0
- package/lib/doctor.js +82 -0
- package/lib/eval.js +35 -2
- package/lib/i18n.js +4 -0
- package/lib/meta.js +29 -1
- package/lib/runbook.js +93 -0
- package/lib/shared.js +55 -0
- package/package.json +1 -1
package/bin/ts.js
CHANGED
|
@@ -1007,6 +1007,37 @@ async function evalCmd(words) {
|
|
|
1007
1007
|
process.exit(2);
|
|
1008
1008
|
}
|
|
1009
1009
|
|
|
1010
|
+
// ── MODEL LAB: --modelos m1,m2,m3 → bake-off automático (mesma suíte em cada executor) ──
|
|
1011
|
+
const _modelsArg = _val(['--modelos', '--models']);
|
|
1012
|
+
if (_modelsArg) {
|
|
1013
|
+
const models = _modelsArg.split(',').map(s => s.trim()).filter(Boolean);
|
|
1014
|
+
if (models.length < 2) { _die(en ? '--modelos needs at least 2 models (comma-separated)' : '--modelos precisa de pelo menos 2 modelos (separados por vírgula)', 2); }
|
|
1015
|
+
if (!JSON_OUT) {
|
|
1016
|
+
console.log('\n ' + ui.gradient('⌁ ' + (en ? 'Model Lab' : 'Model Lab')) + ' ' + C.bold(suite.name) + C.dim(' · ' + suite.cases.length + (en ? ' case(s) × ' : ' caso(s) × ') + models.length + (en ? ' models' : ' modelos')));
|
|
1017
|
+
console.log(' ' + C.dim(en ? 'same suite on each executor; a fixed judge scores all (fair). Costs credits.' : 'mesma suíte em cada executor; um juiz fixo pontua todos (justo). Gasta créditos.') + '\n');
|
|
1018
|
+
}
|
|
1019
|
+
const spM = JSON_OUT ? { start() {}, stop() {}, text() {} } : ui.spinner('…').start();
|
|
1020
|
+
let mx;
|
|
1021
|
+
try {
|
|
1022
|
+
mx = await evalMod.runMatrix(suite, {
|
|
1023
|
+
token, lang: cfg.lang || 'pt', judgeModel, models,
|
|
1024
|
+
onModel: ({ model, phase, row }) => { if (JSON_OUT) return; if (phase === 'start') spM.text((en ? 'testing ' : 'testando ') + model + '…'); else if (phase === 'done') { spM.stop(); console.log(' ' + C.cyan('◆') + ' ' + C.bold(model) + C.dim(' ' + row.passed + '/' + row.total + ' · score ' + row.avgScore + ' · ' + row.credits + ' cr') + (row.error ? C.err(' ' + row.error) : '')); spM.start(); } },
|
|
1025
|
+
onCase: ({ phase, id, pass, score }) => { if (!JSON_OUT && phase === 'done') { spM.stop(); console.log(' ' + (pass ? C.ok('✔') : C.err('✗')) + C.dim(' ' + id + ' ' + score)); spM.start(); } },
|
|
1026
|
+
});
|
|
1027
|
+
} catch (e) { spM.stop(); if (e && (e.code === 'no_credits' || e.status === 402)) { console.error('\n' + ui.errLine(en ? 'AI limit reached — top up to run the bake-off.' : 'Limite de IA atingido — recarregue pra rodar o bake-off.')); process.exit(1); } throw e; }
|
|
1028
|
+
spM.stop();
|
|
1029
|
+
if (JSON_OUT) { console.log(JSON.stringify(mx)); return; }
|
|
1030
|
+
// placar ordenado (qualidade → custo)
|
|
1031
|
+
console.log('\n' + ui.box([
|
|
1032
|
+
C.bold(en ? 'Bake-off ranking' : 'Placar do bake-off') + C.dim(' (' + (en ? 'quality then cost' : 'qualidade depois custo') + ')'),
|
|
1033
|
+
'',
|
|
1034
|
+
...mx.ranked.map((r, i) => (i === 0 ? C.ok('①') : C.dim(' ' + (i + 1) + ' ')) + ' ' + C.bold(r.model.padEnd(20)) + C.dim(r.passed + '/' + r.total + ' ok · score ' + r.avgScore + ' · ' + r.credits + ' cr · ' + (r.ms / 1000).toFixed(1) + 's' + (r.error ? ' ⚠ ' + r.error : ''))),
|
|
1035
|
+
'',
|
|
1036
|
+
C.dim(en ? 'winner: ' : 'vencedor: ') + C.ok(C.bold(mx.winner || '—')),
|
|
1037
|
+
], { title: 'ts model lab' }));
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1010
1041
|
if (!JSON_OUT) {
|
|
1011
1042
|
console.log('\n ' + ui.gradient('⌁ ' + (en ? 'Eval' : 'Avaliação')) + ' ' + C.bold(suite.name) + C.dim(' · ' + suite.cases.length + (en ? ' case(s)' : ' caso(s)')));
|
|
1012
1043
|
console.log(' ' + C.dim(en ? 'runs the real agent per case (costs credits), an AI judge scores each' : 'roda o agente de verdade por caso (gasta créditos); um juiz de IA pontua cada um') + '\n');
|
|
@@ -1200,6 +1231,7 @@ async function metaCmd() {
|
|
|
1200
1231
|
}
|
|
1201
1232
|
|
|
1202
1233
|
const existing = metaMod.load(dir);
|
|
1234
|
+
if (FLAGS.has('--trilha') || FLAGS.has('--trail')) return trilhaCmd();
|
|
1203
1235
|
if (FLAGS.has('--status')) {
|
|
1204
1236
|
if (!existing) { console.log(ui.infoLine(T.meta_none)); return; }
|
|
1205
1237
|
const done = existing.checklist.filter(i => i.passes).length;
|
|
@@ -1734,6 +1766,130 @@ async function verificarCmd(args) {
|
|
|
1734
1766
|
process.exit(rep.allOk ? 0 : 1);
|
|
1735
1767
|
}
|
|
1736
1768
|
|
|
1769
|
+
// ── ts runbook — procedimentos DevOps reexecutáveis e auto-verificáveis ──────
|
|
1770
|
+
async function runbookCmd(args) {
|
|
1771
|
+
const en = (cfg.lang === 'en');
|
|
1772
|
+
const rb = require('../lib/runbook');
|
|
1773
|
+
const sub = String(args[0] || 'listar').toLowerCase();
|
|
1774
|
+
const nome = args.slice(1).find(a => !a.startsWith('-'));
|
|
1775
|
+
|
|
1776
|
+
if (sub === 'listar' || sub === 'ls' || sub === 'list') {
|
|
1777
|
+
const l = rb.list();
|
|
1778
|
+
if (!l.length) { console.log('\n' + ui.infoLine(en ? 'no runbooks yet — create one: ts runbook novo <name>' : 'nenhum runbook ainda — crie um: ts runbook novo <nome>')); return; }
|
|
1779
|
+
console.log('\n' + ui.box([
|
|
1780
|
+
C.bold(en ? 'Runbooks' : 'Runbooks') + C.dim(' (' + l.length + ')'),
|
|
1781
|
+
...l.map(n => { const r = rb.load(n) || {}; return ' ' + C.bold(n) + C.dim(' ' + (r.passos ? r.passos.length : 0) + ' ' + (en ? 'step(s)' : 'passo(s)') + ' · ' + (r.criterios ? r.criterios.length : 0) + ' ' + (en ? 'check(s)' : 'checagem(ns)') + (r.descricao ? ' — ' + String(r.descricao).slice(0, 40) : '')); }),
|
|
1782
|
+
'', C.dim(en ? 'run: ts runbook rodar <name> · edit the .json in ~/.ts/runbooks/' : 'rodar: ts runbook rodar <nome> · edite o .json em ~/.ts/runbooks/'),
|
|
1783
|
+
], { title: 'ts runbook' }));
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1786
|
+
if (sub === 'novo' || sub === 'new' || sub === 'criar') {
|
|
1787
|
+
if (!nome) { console.log(ui.errLine(en ? 'usage: ts runbook novo <name>' : 'uso: ts runbook novo <nome>')); return; }
|
|
1788
|
+
const o = rb.save(nome, rb.template(nome));
|
|
1789
|
+
console.log('\n' + ui.box([C.ok(en ? 'Runbook created' : 'Runbook criado') + C.dim(' ' + o.nome), C.dim(require('../lib/runbook').DIR + require('path').sep + o.nome + '.json'), C.dim(en ? 'edit it (passos + criterios), then: ts runbook rodar ' + o.nome : 'edite (passos + criterios) e rode: ts runbook rodar ' + o.nome)], { title: 'ts runbook' }));
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
if (sub === 'ver' || sub === 'show' || sub === 'cat') {
|
|
1793
|
+
const r = rb.load(nome); if (!r) { console.log(ui.errLine(en ? 'no such runbook' : 'runbook não encontrado')); return; }
|
|
1794
|
+
const v = rb.validate(r);
|
|
1795
|
+
console.log('\n' + ui.box([
|
|
1796
|
+
C.bold(r.nome) + (r.descricao ? C.dim(' — ' + r.descricao) : '') + (v.ok ? C.ok(' ✓') : C.err(' ✗ ' + v.erros.join('; '))),
|
|
1797
|
+
'', C.bold(en ? 'STEPS' : 'PASSOS') + ':',
|
|
1798
|
+
...(r.passos || []).map((p, i) => C.dim(' ' + (i + 1) + '. ') + (p.desc ? C.bold(p.desc) + C.dim(' ') : '') + C.cyan(String(p.cmd).slice(0, 60)) + (p.remoto ? C.dim(' @' + String(p.remoto).split(' ').pop()) : '') + (require('../lib/core').isDestructive(p.cmd) ? C.warn(' ⚠ destrutivo') : '')),
|
|
1799
|
+
'', C.bold(en ? 'PROOF' : 'PROVA') + ':',
|
|
1800
|
+
...(r.criterios || []).map(c => C.dim(' • ') + C.dim(require('../lib/verify')._labelFor(c))),
|
|
1801
|
+
], { title: 'ts runbook' }));
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
if (sub === 'remover' || sub === 'rm' || sub === 'remove' || sub === 'del') {
|
|
1805
|
+
console.log('\n' + (rb.remove(nome) ? ui.infoLine((en ? 'removed ' : 'removido ') + nome) : ui.errLine(en ? 'no such runbook' : 'runbook não encontrado')));
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
if (sub === 'rodar' || sub === 'run' || sub === 'exec') {
|
|
1809
|
+
const r = rb.load(nome); if (!r) { console.log(ui.errLine(en ? 'no such runbook' : 'runbook não encontrado')); return; }
|
|
1810
|
+
console.log('\n' + C.dim(en ? 'running runbook ' : 'rodando runbook ') + C.bold(r.nome) + C.dim(' — ' + (r.passos || []).length + (en ? ' step(s)' : ' passo(s)')) + '\n');
|
|
1811
|
+
const out = await rb.run(r, {
|
|
1812
|
+
cwd: process.cwd(),
|
|
1813
|
+
askApprove: async (cmd) => {
|
|
1814
|
+
if (YES || !process.stdin.isTTY) return false; // não-interativo não aprova destrutivo
|
|
1815
|
+
const a = await ui.ask(C.warn(' ▲ ' + (en ? 'destructive step: ' : 'passo destrutivo: ')) + cmd + C.dim(' [s/N] '));
|
|
1816
|
+
return ['s', 'sim', 'y', 'yes'].includes(String(a).trim().toLowerCase());
|
|
1817
|
+
},
|
|
1818
|
+
onStep: (e) => {
|
|
1819
|
+
if (e.phase === 'start') console.log(' ' + C.indigo('◆') + ' ' + (e.desc ? C.bold(e.desc) + C.dim(' · ') : '') + C.dim(e.cmd.slice(0, 60)));
|
|
1820
|
+
else if (e.phase === 'done') console.log(' ' + (e.ok ? C.ok('✔ exit 0') : C.err('✗ exit ' + e.code)) + (e.ok ? '' : C.dim(' ' + String(e.out || '').split('\n').slice(-1)[0].slice(0, 80))));
|
|
1821
|
+
else if (e.phase === 'refused' || e.phase === 'denied') console.log(' ' + C.err('■ ' + (e.motivo || 'bloqueado')));
|
|
1822
|
+
else if (e.phase === 'verify') console.log('\n ' + C.dim((en ? 'proving result — ' : 'provando o resultado — ') + e.total + (en ? ' check(s)…' : ' checagem(ns)…')));
|
|
1823
|
+
},
|
|
1824
|
+
});
|
|
1825
|
+
if (out.verify) for (const vr of out.verify.results) console.log(' ' + (vr.ok ? C.ok('✔') : C.err('✗')) + ' ' + C.dim((vr.label || '') + (vr.ok ? '' : ' — ' + (vr.detail || ''))));
|
|
1826
|
+
if (out.ok) {
|
|
1827
|
+
console.log('\n' + ui.okLine(C.bold('RUNBOOK OK') + C.dim(en ? ' — steps ran and the result is proven' : ' — passos rodaram e o resultado está provado')));
|
|
1828
|
+
} else {
|
|
1829
|
+
const _why = out.erro || (out.verify && !out.verify.allOk ? (en ? 'proof failed' : 'a prova falhou') : '');
|
|
1830
|
+
console.log('\n' + ui.errLine(C.bold(en ? 'RUNBOOK FAILED' : 'RUNBOOK FALHOU') + C.dim(' ' + _why)));
|
|
1831
|
+
}
|
|
1832
|
+
process.exit(out.ok ? 0 : 1);
|
|
1833
|
+
}
|
|
1834
|
+
console.log(ui.infoLine(en ? 'ts runbook: listar | novo <name> | ver <name> | rodar <name> | remover <name>' : 'ts runbook: listar | novo <nome> | ver <nome> | rodar <nome> | remover <nome>'));
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
// ── ts trilha — timeline AUDITÁVEL da missão (Evidence Ledger: por que está done) ──
|
|
1838
|
+
function trilhaCmd() {
|
|
1839
|
+
const en = (cfg.lang === 'en');
|
|
1840
|
+
const metaMod = require('../lib/meta');
|
|
1841
|
+
const st = metaMod.load(process.cwd());
|
|
1842
|
+
if (!st) { console.log('\n' + ui.infoLine(en ? 'no mission in this folder (run ts meta first)' : 'nenhuma missão nesta pasta (rode ts meta primeiro)')); return; }
|
|
1843
|
+
const t = metaMod.trailSummary(st, cfg.lang);
|
|
1844
|
+
if (JSON_OUT) { console.log(JSON.stringify(t)); return; }
|
|
1845
|
+
const _mark = { done: C.ok('✔'), blocked: C.err('!'), pending: C.dim('○') };
|
|
1846
|
+
const lines = [
|
|
1847
|
+
C.bold((en ? 'Goal: ' : 'Objetivo: ')) + C.dim(String(t.goal).slice(0, 66)),
|
|
1848
|
+
(t.verification.ok ? C.ok : C.warn)(t.verification.icon + ' ' + t.verification.txt) + C.dim(' · ' + t.status + ' · ' + t.progress.done + '/' + t.progress.total + (en ? ' items' : ' itens')),
|
|
1849
|
+
'',
|
|
1850
|
+
C.bold(en ? 'CHECKLIST' : 'CHECKLIST') + ':',
|
|
1851
|
+
...t.checklist.map(i => ' ' + (_mark[i.state] || '○') + ' ' + C.dim(String(i.desc).slice(0, 70))),
|
|
1852
|
+
'',
|
|
1853
|
+
C.bold(en ? 'TIMELINE' : 'LINHA DO TEMPO') + C.dim(' (' + t.rounds.length + (en ? ' rounds)' : ' rodadas)')),
|
|
1854
|
+
...t.rounds.slice(-8).map(r => ' ' + C.indigo('R' + r.n) + ' ' + C.dim(String(r.item).slice(0, 44)) + C.dim(' · ' + r.steps + 'p · ' + r.credits + 'cr')),
|
|
1855
|
+
'',
|
|
1856
|
+
C.bold(en ? 'GATES' : 'GATES') + ': ' + C.dim((t.gates.build ? C.ok('build✔') : 'build○') + ' ' + (t.gates.run ? C.ok('run✔') : 'run○') + ' ' + (t.gates.visual ? C.ok('visual✔') : 'visual○')),
|
|
1857
|
+
];
|
|
1858
|
+
if (t.proof) {
|
|
1859
|
+
lines.push('', C.bold(en ? 'PROOF (criteria)' : 'PROVA (critérios)') + C.dim(' ' + t.proof.passed + '/' + t.proof.total));
|
|
1860
|
+
for (const r of t.proof.results) lines.push(' ' + (r.ok ? C.ok('✔') : C.err('✗')) + ' ' + C.dim((r.label || '') + (r.ok ? '' : ' — ' + (r.detail || ''))));
|
|
1861
|
+
}
|
|
1862
|
+
lines.push('', C.dim(en ? 'cost: ' : 'custo: ') + t.cost.credits + ' cr · ' + t.cost.rounds + (en ? ' rounds' : ' rodadas') + (t.cost.escalations ? ' · ' + t.cost.escalations + ' esc' : '') + (t.cost.minutes != null ? ' · ' + t.cost.minutes + ' min' : ''));
|
|
1863
|
+
lines.push('', (t.verification.ok ? C.ok : C.warn)(t.verdict));
|
|
1864
|
+
console.log('\n' + ui.box(lines, { title: 'ts trilha' }));
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
// ── ts doctor — auto-diagnóstico do ambiente (Node/login/gateway/versão/deps) ──
|
|
1868
|
+
async function doctorCmd() {
|
|
1869
|
+
const en = (cfg.lang === 'en');
|
|
1870
|
+
const doc = require('../lib/doctor');
|
|
1871
|
+
const sp = JSON_OUT ? { stop() {} } : ui.spinner(en ? 'checking your setup…' : 'checando seu ambiente…').start();
|
|
1872
|
+
const out = await doc.run({
|
|
1873
|
+
cfg, version: pkg.version, base: base(),
|
|
1874
|
+
// backend no ar? 401 conta como "respondeu" (só não autenticado).
|
|
1875
|
+
pingBackend: async () => { try { await api('/api/auth/check', { token: cfg.token, timeoutMs: 8000 }); return true; } catch (e) { return (e && e.status === 401); } },
|
|
1876
|
+
// última versão publicada no npm (best-effort).
|
|
1877
|
+
latestVersion: async () => { try { const r = await fetch('https://registry.npmjs.org/terminal-smart-cli/latest', { signal: AbortSignal.timeout(8000) }); const j = await r.json(); return j && j.version; } catch (_) { return null; } },
|
|
1878
|
+
});
|
|
1879
|
+
sp.stop();
|
|
1880
|
+
if (JSON_OUT) { console.log(JSON.stringify(out)); process.exit(out.summary.healthy ? 0 : 1); }
|
|
1881
|
+
const ic = { ok: C.ok('✔'), warn: C.warn('⚠'), fail: C.err('✗') };
|
|
1882
|
+
const lines = [C.bold(en ? 'Environment check' : 'Diagnóstico do ambiente'), ''];
|
|
1883
|
+
for (const r of out.results) {
|
|
1884
|
+
lines.push(' ' + (ic[r.level] || '·') + ' ' + C.bold(r.nome.padEnd(16)) + C.dim(r.detail || ''));
|
|
1885
|
+
if (r.dica) lines.push(' ' + C.dim('→ ' + r.dica));
|
|
1886
|
+
}
|
|
1887
|
+
const s = out.summary;
|
|
1888
|
+
lines.push('', (s.healthy ? C.ok : C.warn)((s.healthy ? (en ? 'All good' : 'Tudo certo') : (en ? 'Attention needed' : 'Precisa de atenção')) + C.dim(' · ' + s.ok + ' ok · ' + s.warn + ' ' + (en ? 'warn' : 'avisos') + ' · ' + s.fail + ' ' + (en ? 'fail' : 'falhas'))));
|
|
1889
|
+
console.log('\n' + ui.box(lines, { title: 'ts doctor' }));
|
|
1890
|
+
process.exit(s.healthy ? 0 : 1);
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1737
1893
|
// ── ts perfil — personas com COFRE DE MEMÓRIA isolado (Mempalace/Hermes) ──────
|
|
1738
1894
|
function perfilCmd(args) {
|
|
1739
1895
|
const en = (cfg.lang === 'en');
|
|
@@ -1997,6 +2153,9 @@ function recallCmd(args) {
|
|
|
1997
2153
|
case 'buscar': case 'search': case 'procurar': return buscarCmd(POS.slice(1));
|
|
1998
2154
|
case 'perfil': case 'persona': case 'profile': return perfilCmd(POS.slice(1));
|
|
1999
2155
|
case 'verificar': case 'verify': case 'provar': return verificarCmd(POS.slice(1));
|
|
2156
|
+
case 'runbook': case 'runbooks': case 'procedimento': return runbookCmd(POS.slice(1));
|
|
2157
|
+
case 'trilha': case 'trail': case 'auditoria': return trilhaCmd();
|
|
2158
|
+
case 'doctor': case 'diagnostico-ambiente': case 'checkup': return doctorCmd();
|
|
2000
2159
|
case 'meta': case 'missao': case 'mission': return metaCmd();
|
|
2001
2160
|
case 'runs': return runsCmd();
|
|
2002
2161
|
case 'status': return statusCmd(POS[1]);
|
package/lib/doctor.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// ts doctor — AUTO-DIAGNÓSTICO do ambiente do CLI (confiabilidade visível aplicada a si mesmo).
|
|
2
|
+
// Checa o que o ts precisa pra funcionar (Node, login, gateway, versão) e o que habilita
|
|
3
|
+
// recursos opcionais (git, Chrome p/ --navegador, yt-dlp p/ ts video, monolith p/ ts arquivar).
|
|
4
|
+
// Cada check devolve { nome, level: 'ok'|'warn'|'fail', detail, dica? }. Puro onde dá (testável);
|
|
5
|
+
// as checagens de rede degradam gracioso (nunca lançam).
|
|
6
|
+
'use strict';
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { execFileSync } = require('child_process');
|
|
11
|
+
|
|
12
|
+
// ── helpers PUROS (testáveis) ────────────────────────────────────────────────
|
|
13
|
+
function _nodeMajor(v) { const m = String(v || '').replace(/^v/, '').split('.'); return Number(m[0]) || 0; }
|
|
14
|
+
function _nodeOk(v) { return _nodeMajor(v) >= 18; }
|
|
15
|
+
// estado de autenticação a partir do config carregado
|
|
16
|
+
function _authLevel(cfg) {
|
|
17
|
+
if (!cfg || !cfg.token) return { level: 'fail', detail: 'não logado', dica: 'rode: ts login' };
|
|
18
|
+
return { level: 'ok', detail: 'logado' + (cfg.username ? ' como ' + cfg.username : '') + (cfg.plan ? ' · plano ' + cfg.plan : '') };
|
|
19
|
+
}
|
|
20
|
+
// compara versão instalada × última do npm → ok / warn (desatualizado)
|
|
21
|
+
function _versionLevel(current, latest) {
|
|
22
|
+
if (!latest) return { level: 'warn', detail: 'v' + current + ' (não deu pra checar o npm)' };
|
|
23
|
+
if (current === latest) return { level: 'ok', detail: 'v' + current + ' (última)' };
|
|
24
|
+
return { level: 'warn', detail: 'v' + current + ' — última é v' + latest, dica: 'atualize: npm i -g terminal-smart-cli' };
|
|
25
|
+
}
|
|
26
|
+
// resumo agregado
|
|
27
|
+
function _summary(results) {
|
|
28
|
+
return { total: results.length, ok: results.filter(r => r.level === 'ok').length,
|
|
29
|
+
warn: results.filter(r => r.level === 'warn').length, fail: results.filter(r => r.level === 'fail').length,
|
|
30
|
+
healthy: results.every(r => r.level !== 'fail') };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// binário presente? (cross-platform, best-effort — nunca lança)
|
|
34
|
+
function hasBin(name) {
|
|
35
|
+
const cmd = process.platform === 'win32' ? 'where' : 'which';
|
|
36
|
+
try { const out = String(execFileSync(cmd, [name], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 5000 })).trim(); return out ? out.split(/\r?\n/)[0] : null; }
|
|
37
|
+
catch (_) { return null; }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── checks ────────────────────────────────────────────────────────────────────
|
|
41
|
+
// opcionais: quando ausentes → warn (recurso desligado), não fail.
|
|
42
|
+
const OPTIONAL_BINS = [
|
|
43
|
+
{ bin: 'git', para: 'worktrees, versionamento', alt: [] },
|
|
44
|
+
{ bin: 'yt-dlp', para: 'ts video (transcrição)', alt: ['youtube-dl'] },
|
|
45
|
+
{ bin: 'monolith', para: 'ts arquivar (página→HTML)', alt: [] },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
// run(opts): { cfg, version, base, api } — api opcional (fetch do backend/npm). Sempre resolve.
|
|
49
|
+
async function run(opts = {}) {
|
|
50
|
+
const cfg = opts.cfg || {};
|
|
51
|
+
const results = [];
|
|
52
|
+
|
|
53
|
+
// 1) Node
|
|
54
|
+
const nv = process.version;
|
|
55
|
+
results.push({ nome: 'Node.js', level: _nodeOk(nv) ? 'ok' : 'fail', detail: nv + (_nodeOk(nv) ? '' : ' (precisa 18+)'), dica: _nodeOk(nv) ? undefined : 'instale Node 18 ou superior' });
|
|
56
|
+
|
|
57
|
+
// 2) config ~/.ts + login
|
|
58
|
+
const auth = _authLevel(cfg); results.push(Object.assign({ nome: 'Login' }, auth));
|
|
59
|
+
const dir = path.join(os.homedir(), '.ts');
|
|
60
|
+
results.push({ nome: 'Config (~/.ts)', level: fs.existsSync(dir) ? 'ok' : 'warn', detail: fs.existsSync(dir) ? dir : 'ainda não criado (normal se nunca rodou nada)' });
|
|
61
|
+
|
|
62
|
+
// 3) gateway/backend (rede, best-effort)
|
|
63
|
+
if (typeof opts.pingBackend === 'function') {
|
|
64
|
+
try { const ok = await opts.pingBackend(); results.push({ nome: 'Backend', level: ok ? 'ok' : 'warn', detail: ok ? (opts.base || 'acessível') : 'sem resposta', dica: ok ? undefined : 'cheque a conexão / status em terminalsmart.com.br' }); }
|
|
65
|
+
catch (_) { results.push({ nome: 'Backend', level: 'warn', detail: 'sem resposta' }); }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 4) versão vs npm latest (rede, best-effort)
|
|
69
|
+
let latest = null;
|
|
70
|
+
if (typeof opts.latestVersion === 'function') { try { latest = await opts.latestVersion(); } catch (_) {} }
|
|
71
|
+
results.push(Object.assign({ nome: 'Versão' }, _versionLevel(opts.version || '?', latest)));
|
|
72
|
+
|
|
73
|
+
// 5) deps opcionais
|
|
74
|
+
for (const o of OPTIONAL_BINS) {
|
|
75
|
+
let found = hasBin(o.bin); for (const a of o.alt) if (!found) found = hasBin(a);
|
|
76
|
+
results.push({ nome: o.bin, level: found ? 'ok' : 'warn', detail: found ? found : ('ausente — ' + o.para + ' fica indisponível'), dica: found ? undefined : 'opcional; instale se for usar ' + o.para });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { results, summary: _summary(results) };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = { run, hasBin, OPTIONAL_BINS, _test: { _nodeMajor, _nodeOk, _authLevel, _versionLevel, _summary } };
|
package/lib/eval.js
CHANGED
|
@@ -239,5 +239,38 @@ async function runSuite(suite, opts = {}) {
|
|
|
239
239
|
return { summary, results };
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
// ── MODEL LAB: roda a MESMA suíte em VÁRIOS executores e compara (bake-off automático) ──
|
|
243
|
+
// Automatiza o "veredito modelo X" que antes era manual. O JUIZ é fixo (mesmo pra todos =
|
|
244
|
+
// comparação justa); só o EXECUTOR muda. Ranqueia por qualidade VERIFICADA e depois por custo.
|
|
245
|
+
function _rankModels(rows) {
|
|
246
|
+
// ordena: mais casos aprovados (passRate) → maior score médio → MENOR custo → mais rápido.
|
|
247
|
+
const sorted = [...rows].sort((a, b) =>
|
|
248
|
+
(b.passRate - a.passRate) || (b.avgScore - a.avgScore) || (a.credits - b.credits) || (a.ms - b.ms));
|
|
249
|
+
return { ranked: sorted, winner: sorted[0] ? sorted[0].model : null };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function runMatrix(suite, opts = {}) {
|
|
253
|
+
const models = (opts.models || []).filter(Boolean);
|
|
254
|
+
if (!models.length) throw new Error('runMatrix precisa de opts.models (lista de executores)');
|
|
255
|
+
const onModel = opts.onModel || (() => {});
|
|
256
|
+
const rows = [];
|
|
257
|
+
for (const model of models) {
|
|
258
|
+
onModel({ model, phase: 'start' });
|
|
259
|
+
let sum;
|
|
260
|
+
try {
|
|
261
|
+
const r = await runSuite(suite, Object.assign({}, opts, { model, onCase: opts.onCase }));
|
|
262
|
+
sum = r.summary;
|
|
263
|
+
} catch (e) {
|
|
264
|
+
if (e && (e.code === 'no_credits' || e.status === 402)) throw e; // teto → aborta o bake-off
|
|
265
|
+
sum = { name: suite.name, total: suite.cases.length, passed: 0, failed: suite.cases.length, passRate: 0, avgScore: 0, credits: 0, judgeTokens: 0, ms: 0, error: String((e && e.message) || e).slice(0, 160) };
|
|
266
|
+
}
|
|
267
|
+
const row = { model, passRate: sum.passRate, avgScore: sum.avgScore, passed: sum.passed, total: sum.total, credits: sum.credits, judgeTokens: sum.judgeTokens, ms: sum.ms, error: sum.error || null };
|
|
268
|
+
rows.push(row);
|
|
269
|
+
onModel({ model, phase: 'done', row });
|
|
270
|
+
}
|
|
271
|
+
const rank = _rankModels(rows);
|
|
272
|
+
return { suite: suite.name, cases: suite.cases.length, rows, ranked: rank.ranked, winner: rank.winner };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
module.exports = { loadSuite, normCases, runSuite, runMatrix, judge, smokeSuite, EXAMPLE,
|
|
276
|
+
_test: { _diff, _snap, _extractJson, normCases, _rankModels } };
|
package/lib/i18n.js
CHANGED
|
@@ -46,8 +46,11 @@ const STR = {
|
|
|
46
46
|
['ts meta "..." --criterios prova.json', 'só declara "verificada" se os critérios executáveis (verify) passarem de verdade'],
|
|
47
47
|
['ts meta "..." --provar', 'auto-deriva a prova da stack (roda os testes do projeto) — sem escrever json'],
|
|
48
48
|
['ts meta --status', 'estado da missão deste diretório'],
|
|
49
|
+
['ts trilha', 'TIMELINE AUDITÁVEL da missão: checklist + rodadas + gates + PROVA + veredito (por que está pronta)'],
|
|
49
50
|
['ts eval suite.json', 'avalia o agente numa suíte de casos (juiz de IA + nota)'],
|
|
51
|
+
['ts eval suite.json --modelos a,b,c', 'MODEL LAB: bake-off automático — mesma suíte em cada modelo + placar (qualidade→custo)'],
|
|
50
52
|
['ts verificar --cmd "npm test" --url ... --porta N', 'PROVA determinística de conclusão (build/http/porta/arquivo); exit 1 pra CI'],
|
|
53
|
+
['ts runbook rodar <nome>', 'procedimento DevOps reexecutável: roda os passos (destrutivo pede OK) e PROVA o resultado'],
|
|
51
54
|
['ts acp', 'servidor Agent Client Protocol (conecta o ts a editores tipo Zed)'],
|
|
52
55
|
['ts mcp', 'servidores MCP: as ferramentas deles entram no agente'],
|
|
53
56
|
] },
|
|
@@ -61,6 +64,7 @@ const STR = {
|
|
|
61
64
|
['ts login · ts logout', 'conecta / desconecta este terminal'],
|
|
62
65
|
['ts uso', 'créditos e consumo do mês'],
|
|
63
66
|
['ts quem', 'conta conectada'],
|
|
67
|
+
['ts doctor', 'diagnóstico do ambiente (Node, login, gateway, versão, deps opcionais)'],
|
|
64
68
|
['ts idioma pt|en', 'idioma (padrão pt-BR)'],
|
|
65
69
|
] },
|
|
66
70
|
],
|
package/lib/meta.js
CHANGED
|
@@ -1410,4 +1410,32 @@ function verificationLabel(ver, lang) {
|
|
|
1410
1410
|
}
|
|
1411
1411
|
}
|
|
1412
1412
|
|
|
1413
|
-
|
|
1413
|
+
// TRILHA AUDITÁVEL (Evidence Ledger da missão): responde "por que está concluída e como
|
|
1414
|
+
// cheguei aqui" a partir do estado salvo — SEM confiar na narrativa. Função PURA/testável.
|
|
1415
|
+
function trailSummary(st, lang) {
|
|
1416
|
+
const en = lang === 'en';
|
|
1417
|
+
const cl = Array.isArray(st.checklist) ? st.checklist : [];
|
|
1418
|
+
const rounds = Array.isArray(st.rounds) ? st.rounds : [];
|
|
1419
|
+
const ver = verificationLabel(st.verification, lang);
|
|
1420
|
+
const done = cl.filter(i => i.passes).length;
|
|
1421
|
+
const mins = (st.created_at && st.finished_at) ? Math.round((new Date(st.finished_at) - new Date(st.created_at)) / 60000) : null;
|
|
1422
|
+
let verdict;
|
|
1423
|
+
if (st.status !== 'done') verdict = (en ? 'Not finished — status: ' : 'Não concluída — status: ') + st.status + (st.pause_reason ? ' (' + st.pause_reason + ')' : '');
|
|
1424
|
+
else if (st.verification === 'verified') verdict = en ? 'Done and VERIFIED: gates and criteria passed.' : 'Concluída e VERIFICADA: gates e critérios passaram.';
|
|
1425
|
+
else if (st.verification === 'unverified') verdict = en ? 'Ended WITHOUT full proof (fix limit / gate) — check before relying.' : 'Encerrada SEM prova completa (teto de correções / gate) — confira antes de confiar.';
|
|
1426
|
+
else if (st.verification === 'built_not_run') verdict = en ? 'Built, but execution was NOT verified.' : 'Compilou, mas a execução NÃO foi verificada.';
|
|
1427
|
+
else verdict = en ? 'Done (no build/run gate applied for this task).' : 'Concluída (sem gate de build/execução p/ esta tarefa).';
|
|
1428
|
+
return {
|
|
1429
|
+
goal: st.goal, status: st.status, verification: ver,
|
|
1430
|
+
checklist: cl.map(i => ({ id: i.id, desc: i.desc, state: i.passes ? 'done' : i.blocked ? 'blocked' : 'pending' })),
|
|
1431
|
+
progress: { done, total: cl.length },
|
|
1432
|
+
rounds: rounds.map((r, i) => ({ n: i + 1, item: r.item, steps: r.steps || 0, credits: r.credits || 0, snippet: String(r.result || '').replace(/\s+/g, ' ').slice(0, 120) })),
|
|
1433
|
+
gates: { build: !!st.buildVerified, run: !!st.runVerified, visual: !!st.visualOk },
|
|
1434
|
+
proof: st.verifyReport && st.verifyReport.total ? { passed: st.verifyReport.passed, total: st.verifyReport.total, results: st.verifyReport.results } : null,
|
|
1435
|
+
cost: { credits: st.creditsSpent || 0, rounds: rounds.length, escalations: st.escalations || 0, minutes: mins },
|
|
1436
|
+
note: st.runNote || null,
|
|
1437
|
+
verdict,
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
module.exports = { webRunGate, run, load, notify, stateFile, detectBuild, ensureToolchain, runBuild, escalate, designPhase, archPhase, looksVisual, looksComplex, runApp, visualGate, ensureEmulator, _projKind, _llmVision, verificationLabel, _checkCriteria, trailSummary };
|
package/lib/runbook.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// RUNBOOKS EXECUTÁVEIS — procedimentos DevOps reexecutáveis e AUTO-VERIFICÁVEIS.
|
|
2
|
+
// Insight das análises externas (GPT): "transforme uma execução aprovada e bem-sucedida em
|
|
3
|
+
// runbook tipado; na próxima vez o harness roda determinístico e só chama IA se houver desvio".
|
|
4
|
+
// Um runbook = PASSOS (comandos) + CRITÉRIOS (verify). Roda os passos (destrutivo pede
|
|
5
|
+
// aprovação; auto-destrutivo é recusado na hora) e PROVA o resultado com os verificadores.
|
|
6
|
+
// Zero IA por padrão. Ex.: "deploy", "backup do banco", "configurar nginx", "renovar SSL".
|
|
7
|
+
'use strict';
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { execSync } = require('child_process');
|
|
12
|
+
const core = require('./core');
|
|
13
|
+
const tools = require('./tools');
|
|
14
|
+
const verify = require('./verify');
|
|
15
|
+
|
|
16
|
+
const DIR = path.join(os.homedir(), '.ts', 'runbooks');
|
|
17
|
+
const file = (nome) => path.join(DIR, _slug(nome) + '.json');
|
|
18
|
+
function _slug(n) { return String(n || '').toLowerCase().replace(/[^a-z0-9_-]+/g, '-').replace(/^-+|-+$/g, '') || 'runbook'; }
|
|
19
|
+
|
|
20
|
+
// ── persistência ─────────────────────────────────────────────────────────────
|
|
21
|
+
function list() {
|
|
22
|
+
try { return fs.readdirSync(DIR).filter(f => f.endsWith('.json')).map(f => f.replace(/\.json$/, '')); } catch (_) { return []; }
|
|
23
|
+
}
|
|
24
|
+
function load(nome) { try { return JSON.parse(fs.readFileSync(file(nome), 'utf8')); } catch (_) { return null; } }
|
|
25
|
+
function save(nome, rb) {
|
|
26
|
+
fs.mkdirSync(DIR, { recursive: true });
|
|
27
|
+
const obj = Object.assign({}, rb, { nome: _slug(nome) });
|
|
28
|
+
fs.writeFileSync(file(nome), JSON.stringify(obj, null, 2));
|
|
29
|
+
return obj;
|
|
30
|
+
}
|
|
31
|
+
function remove(nome) { try { fs.unlinkSync(file(nome)); return true; } catch (_) { return false; } }
|
|
32
|
+
|
|
33
|
+
// template inicial pro `ts runbook novo`
|
|
34
|
+
function template(nome) {
|
|
35
|
+
return {
|
|
36
|
+
nome: _slug(nome), descricao: '',
|
|
37
|
+
passos: [{ desc: 'exemplo — troque pelo comando real', cmd: 'echo hello', remoto: '' }],
|
|
38
|
+
criterios: [{ type: 'command', cmd: 'echo ok', exit: 0, label: 'prova de que deu certo' }],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── validação (determinística) ────────────────────────────────────────────────
|
|
43
|
+
function validate(rb) {
|
|
44
|
+
const erros = [];
|
|
45
|
+
if (!rb || typeof rb !== 'object') return { ok: false, erros: ['runbook vazio/ inválido'] };
|
|
46
|
+
const passos = Array.isArray(rb.passos) ? rb.passos : [];
|
|
47
|
+
if (!passos.length) erros.push('sem passos');
|
|
48
|
+
passos.forEach((p, i) => { if (!p || !String(p.cmd || '').trim()) erros.push('passo ' + (i + 1) + ' sem "cmd"'); });
|
|
49
|
+
const crit = Array.isArray(rb.criterios) ? rb.criterios : [];
|
|
50
|
+
const comp = verify.compileCriteria(crit);
|
|
51
|
+
if (crit.length && !comp.criteria.length) erros.push('nenhum critério válido (' + crit.length + ' descartado[s])');
|
|
52
|
+
return { ok: erros.length === 0, erros, criterios: comp.criteria, droppedCriterios: comp.dropped.length };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── execução ──────────────────────────────────────────────────────────────────
|
|
56
|
+
// Roda 1 comando (local ou ssh remoto), captura code/saída — nunca lança.
|
|
57
|
+
function _run(cmd, remoto) {
|
|
58
|
+
const full = remoto ? `${remoto} ${JSON.stringify(cmd)}` : cmd;
|
|
59
|
+
try { const out = execSync(full, { encoding: 'utf8', timeout: 600000, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true }); return { code: 0, out }; }
|
|
60
|
+
catch (e) { return { code: typeof e.status === 'number' ? e.status : 1, out: String((e.stdout || '') + (e.stderr || '') || e.message || '').slice(0, 4000) }; }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// opts: { cwd, onStep({idx,total,desc,cmd,phase,code,ok}), askApprove(cmd)->bool, yes }
|
|
64
|
+
// Passo destrutivo (core.isDestructive) → askApprove; auto-destrutivo local → RECUSA na hora.
|
|
65
|
+
// Um passo que falha (code≠0) INTERROMPE a execução (não segue com o ambiente meio-quebrado).
|
|
66
|
+
// No fim, roda os CRITÉRIOS (verify) e devolve o veredito honesto.
|
|
67
|
+
async function run(rb, opts = {}) {
|
|
68
|
+
const cwd = opts.cwd || process.cwd();
|
|
69
|
+
const onStep = opts.onStep || (() => {});
|
|
70
|
+
const askApprove = opts.askApprove || (async () => false);
|
|
71
|
+
const v = validate(rb);
|
|
72
|
+
if (!v.ok) return { ok: false, erro: 'runbook inválido: ' + v.erros.join('; '), passos: [], verify: null };
|
|
73
|
+
|
|
74
|
+
const passos = rb.passos, total = passos.length, done = [];
|
|
75
|
+
for (let i = 0; i < total; i++) {
|
|
76
|
+
const p = passos[i]; const cmd = String(p.cmd).trim(); const remoto = p.remoto || '';
|
|
77
|
+
onStep({ idx: i, total, desc: p.desc || '', cmd, phase: 'start' });
|
|
78
|
+
// auto-destrutivo LOCAL (mata a própria máquina/missão) → recusa imediata, sem aprovação
|
|
79
|
+
if (!remoto) { const sd = tools.selfDestructiveReason(cmd, { cwd }); if (sd) { const r = { idx: i, cmd, ok: false, code: -1, motivo: sd }; done.push(r); onStep({ ...r, phase: 'refused' }); return { ok: false, erro: 'passo ' + (i + 1) + ' recusado: ' + sd, passos: done, verify: null }; } }
|
|
80
|
+
// destrutivo → precisa de aprovação (a menos de --yes, que o chamador decide via askApprove)
|
|
81
|
+
if (core.isDestructive(cmd)) { const okA = await askApprove(cmd); if (!okA) { const r = { idx: i, cmd, ok: false, code: -1, motivo: 'não aprovado' }; done.push(r); onStep({ ...r, phase: 'denied' }); return { ok: false, erro: 'passo ' + (i + 1) + ' (destrutivo) não aprovado', passos: done, verify: null }; } }
|
|
82
|
+
const res = _run(cmd, remoto);
|
|
83
|
+
const r = { idx: i, cmd, ok: res.code === 0, code: res.code, out: String(res.out).slice(-300) };
|
|
84
|
+
done.push(r); onStep({ ...r, phase: 'done' });
|
|
85
|
+
if (!r.ok) return { ok: false, erro: 'passo ' + (i + 1) + ' falhou (exit ' + res.code + ')', passos: done, verify: null };
|
|
86
|
+
}
|
|
87
|
+
// PROVA: roda os critérios de verificação
|
|
88
|
+
let rep = null;
|
|
89
|
+
if (v.criterios.length) { onStep({ phase: 'verify', total: v.criterios.length }); rep = await verify.runAll(v.criterios, { cwd }); }
|
|
90
|
+
return { ok: !rep || rep.allOk, passos: done, verify: rep };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { list, load, save, remove, template, validate, run, DIR, _slug };
|
package/lib/shared.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// ⌁ NÚCLEO CANÔNICO PORTÁVEL do Terminal Smart — o "@terminal-smart/agent-core" mínimo.
|
|
2
|
+
//
|
|
3
|
+
// Convergência F4 (análises GPT): a Web e o App reimplementavam de forma divergente a mesma
|
|
4
|
+
// lógica de gate/erro/verificação → risco de comportamento inconsistente entre superfícies
|
|
5
|
+
// (um comando bloqueado no CLI e liberado no App). Este ponto de entrada reúne o subconjunto
|
|
6
|
+
// do motor que NÃO depende do CLI (nada de api/ui/config/agent/tools) — só Node builtins e
|
|
7
|
+
// estes 4 módulos — pra QUALQUER superfície consumir a MESMA fonte de verdade.
|
|
8
|
+
//
|
|
9
|
+
// Como a Web/App consomem: copiar core.js + verify.js + recovery.js + stack.js + shared.js
|
|
10
|
+
// para o backend (ou publicar como pacote npm e depender dele). ZERO mudança de runtime.
|
|
11
|
+
//
|
|
12
|
+
// Garantia: test/core.test.js tem um GUARDA que falha se qualquer um destes módulos passar a
|
|
13
|
+
// importar algo específico do CLI — mantendo o núcleo portável para sempre.
|
|
14
|
+
'use strict';
|
|
15
|
+
const core = require('./core');
|
|
16
|
+
const verify = require('./verify');
|
|
17
|
+
const recovery = require('./recovery');
|
|
18
|
+
const stack = require('./stack');
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
// namespaces completos
|
|
22
|
+
core, verify, recovery, stack,
|
|
23
|
+
|
|
24
|
+
// ── atalhos das capacidades mais usadas (a superfície importa daqui) ──
|
|
25
|
+
// Gate de destrutivo (fonte única — App/Web param de ter regex divergente)
|
|
26
|
+
isDestructive: core.isDestructive,
|
|
27
|
+
DESTRUCTIVE: core.DESTRUCTIVE,
|
|
28
|
+
// ToolResult tipado + classes de erro (erro nunca vira "concluído")
|
|
29
|
+
classifyToolResult: core.classifyToolResult,
|
|
30
|
+
classifyError: core.classifyError,
|
|
31
|
+
ERROR_CLASSES: core.ERROR_CLASSES,
|
|
32
|
+
RETRYABLE_CLASSES: core.RETRYABLE_CLASSES,
|
|
33
|
+
// Janela de contexto / compactação
|
|
34
|
+
winFor: core.winFor,
|
|
35
|
+
estMsgsTok: core.estMsgsTok,
|
|
36
|
+
COMPACT_AT: core.COMPACT_AT,
|
|
37
|
+
KEEP_TAIL: core.KEEP_TAIL,
|
|
38
|
+
DEFAULT_EXECUTOR: core.DEFAULT_EXECUTOR,
|
|
39
|
+
// Envelope de eventos (contrato stream-json único)
|
|
40
|
+
AgentEvents: core.AgentEvents,
|
|
41
|
+
EVENT_TYPES: core.EVENT_TYPES,
|
|
42
|
+
// Verificadores + TaskSpec (o modelo propõe, o harness valida e prova)
|
|
43
|
+
runCriteria: verify.runAll,
|
|
44
|
+
runCriterion: verify.runCriterion,
|
|
45
|
+
compileCriteria: verify.compileCriteria,
|
|
46
|
+
deriveFromStack: verify.deriveFromStack,
|
|
47
|
+
// Recovery Engine (estratégia de conserto por classe de erro)
|
|
48
|
+
recoveryHint: recovery.recoveryHint,
|
|
49
|
+
strategiesFor: recovery.strategiesFor,
|
|
50
|
+
// Detector de stack
|
|
51
|
+
detectStack: stack.detectStack,
|
|
52
|
+
|
|
53
|
+
// versão do contrato (bump quando a forma dos exports/eventos mudar)
|
|
54
|
+
CORE_CONTRACT: 1,
|
|
55
|
+
};
|